Non-random allocation of tiles

library(ggplot2)
library(imager)
Loading required package: magrittr

Attaching package: ‘imager’

The following object is masked from ‘package:magrittr’:

    add

The following objects are masked from ‘package:stats’:

    convolve, spectrum

The following object is masked from ‘package:graphics’:

    frame

The following object is masked from ‘package:base’:

    save.image
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(lwgeom)
Linking to liblwgeom 3.0.0beta1 r16016, GEOS 3.9.1, PROJ 7.2.1
library(MexBrewer)
library(purrr)

Attaching package: ‘purrr’

The following object is masked from ‘package:magrittr’:

    set_names
library(sf)
Linking to GEOS 3.9.1, GDAL 3.2.1, PROJ 7.2.1; sf_use_s2() is TRUE
library(truchet)

ggplot() + geom_sf(data = mosaic %>% st_buffer(dist = c(0.15)) %>% st_union(), color = “dodgerblue4”, fill = “dodgerblue1”, size = 1) + geom_sf(data = mosaic %>% st_buffer(dist = c(0.05)), size = 0.5)

Create data frame for the mosaic:

xlim <- c(0, 10)
ylim <- c(0, 10)

# Create a data frame with the spots for tiles
container <- expand.grid(x = seq(xlim[1], xlim[2], 1),
                         y = seq(ylim[1], ylim[2], 1)) %>%
  mutate(tiles = case_when(x <= 2 | x >= 8 ~ "dl", 
                           x > 2 & x <8 ~ "dr"))
st_truchet_ss(df = container) %>%
  ggplot() +
  geom_sf()

Create data frame for the mosaic:

xlim <- c(0, 10)
ylim <- c(0, 10)

# Create a data frame with the spots for tiles
container <- expand.grid(x = seq(xlim[1], xlim[2], 1),
                         y = seq(ylim[1], ylim[2], 1)) %>%
  mutate(tiles = sample(c("dl", "dr"), n(), replace = TRUE))

Create mosaic using the designed container:

mosaic <- st_truchet_ss(df = container)

Plot mosaic:

Plot with some embellishments:

Plot with some embellishments:

Create a polygon to contain the mosaic:

 container <- matrix(c(0, 0,
                   0, 10,
                   10, 10,
                   10, 0,
                   0, 0),
                 ncol = 2,
                 byrow = TRUE)

  # Convert coordinates to polygons and then to simple features
  tile <- data.frame(geometry = sf::st_polygon(list(tile)) %>%
                       sf::st_sfc()) %>%
    sf::st_as_sf()
Error in stopifnot(is.list(x)) : object 'tile' not found

Split container

container <- container%>%
  st_split(mosaic %>% st_union())

Extract geometries:

container <- container %>% 
  st_collection_extract(c("POLYGON"))

Plot container:

Something interesting happens with positive buffers:

ggsave("single-scale-polygons-revolucion-positive-buffers-2.png")
Saving 7 x 7 in image

Something interesting happens with positive buffers:

ggsave("single-scale-polygons-revolucion-positive-buffers-3.png")
Saving 7 x 7 in image

Using images

library(imager)
Loading required package: magrittr

Attaching package: ‘imager’

The following object is masked from ‘package:magrittr’:

    add

The following objects are masked from ‘package:stats’:

    convolve, spectrum

The following object is masked from ‘package:graphics’:

    frame

The following object is masked from ‘package:base’:

    save.image

Read the image using imager::load.image():

marilyn <- load.image("marilyn.jpg")

Image info:

marilyn
Image. Width: 800 pix Height: 1200 pix Depth: 1 Colour channels: 1 
plot(marilyn)

Resize image:

marilyn_rs <- imresize(marilyn, scale = 1/15, interpolation = 6)

Convert to data frame:

marilyn_df <- marilyn_rs %>%
  #grayscale() %>% 
  as.data.frame() %>%
  mutate(y = -(y - max(y)))
ggplot() +
  geom_point(data = marilyn_df,
             aes(x, y, color = value)) +
  coord_equal()

Create data frame for the mosaic:

xlim <- c(min(marilyn_df$x)/8 - 2, max(marilyn_df$x)/8 + 2)
ylim <- c(min(marilyn_df$y)/8 - 2, max(marilyn_df$y)/8 + 2)

# Create a data frame with the spots for tiles
mosaic <- expand.grid(x = seq(xlim[1], xlim[2], 1),
                         y = seq(ylim[1], ylim[2], 1)) %>%
  mutate(tiles = sample(c("dl", "dr"), n(), replace = TRUE))

Create mosaic using the designed container:

mosaic <- st_truchet_ss(df = mosaic)

Plot mosaic:

ggplot() +
  geom_sf(data = mosaic,
          size = 1)

Now this needs to be scaled to the size of the image. First get the union of the geometries:

mosaic_union <- st_union(mosaic)

Then scale and recenter:

mosaic_union <- mosaic_union * 8 

Create a polygon to contain the mosaic:

 container <- matrix(c(min(marilyn_df$x), min(marilyn_df$y),
                   min(marilyn_df$x), max(marilyn_df$y),
                   max(marilyn_df$x), max(marilyn_df$y),
                   max(marilyn_df$x), min(marilyn_df$y),
                   min(marilyn_df$x), min(marilyn_df$y)),
                 ncol = 2,
                 byrow = TRUE)

  # Convert coordinates to polygons and then to simple features
  container <- data.frame(geometry = sf::st_polygon(list(container)) %>%
                       sf::st_sfc()) %>%
    sf::st_as_sf()

Split container

mosaic <- container %>%
  st_split(mosaic_union)

Extract geometries:

mosaic <- mosaic %>% 
  st_collection_extract(c("POLYGON"))

Create buffers:

mosaic_1 <- mosaic %>%
  st_buffer(dist = -1)

mosaic_2 <- mosaic %>%
  st_buffer(dist = -2)

mosaic_3 <- mosaic %>%
  st_buffer(dist = -3)

Plot mosaic:

ggsave("marilyn-truchet.png")
Saving 7 x 7 in image

Changing the width of the lines

Load package:

library(imager)
Loading required package: magrittr

Attaching package: ‘imager’

The following object is masked from ‘package:magrittr’:

    add

The following objects are masked from ‘package:stats’:

    convolve, spectrum

The following object is masked from ‘package:graphics’:

    frame

The following object is masked from ‘package:base’:

    save.image
library(purrr)

Attaching package: ‘purrr’

The following object is masked from ‘package:magrittr’:

    set_names

Read the image using imager::load.image():

marilyn <- load.image("marilyn.jpg")

Image info:

marilyn
Image. Width: 800 pix Height: 1200 pix Depth: 1 Colour channels: 1 
plot(marilyn)

Resize image:

marilyn_rs <- imresize(marilyn, scale = 1/4, interpolation = 6)

Convert to data frame:

marilyn_df <- marilyn_rs %>%
  #grayscale() %>% 
  as.data.frame() %>%
  mutate(y = -(y - max(y)))
ggplot() +
  geom_point(data = marilyn_df,
             aes(x, y, color = value)) +
  coord_equal()

Create data frame for the mosaic:

s <- 8

xlim <- c(min(marilyn_df$x)/s - 2, max(marilyn_df$x)/s + 2)
ylim <- c(min(marilyn_df$y)/s - 2, max(marilyn_df$y)/s + 2)

# Create a data frame with the spots for tiles
mosaic <- expand.grid(x = seq(xlim[1], xlim[2], 1),
                      y = seq(ylim[1], ylim[2], 1)) %>%
  mutate(tiles = sample(c("dl", "dr"), n(), replace = TRUE))

Create mosaic using the designed container:

mosaic <- st_truchet_ss(df = mosaic)

Plot mosaic:

ggplot() +
  geom_sf(data = mosaic,
          size = 1)

Now this needs to be scaled to the size of the image. First get the union of the geometries:

mosaic_union <- st_union(mosaic)

Then scale and recenter:

mosaic_union <- mosaic_union * s 

Create a polygon to contain the mosaic:

container <- matrix(c(min(marilyn_df$x), min(marilyn_df$y),
                      min(marilyn_df$x), max(marilyn_df$y),
                      max(marilyn_df$x), max(marilyn_df$y),
                      max(marilyn_df$x), min(marilyn_df$y),
                      min(marilyn_df$x), min(marilyn_df$y)),
                    ncol = 2,
                    byrow = TRUE)

# Convert coordinates to polygons and then to simple features
container <- data.frame(geometry = sf::st_polygon(list(container)) %>%
                          sf::st_sfc()) %>%
  sf::st_as_sf()

Split container

mosaic <- container %>%
  st_split(mosaic_union)

Extract geometries:

mosaic <- mosaic %>% 
  st_collection_extract(c("POLYGON"))

Create buffers:

mosaic_1 <- mosaic %>%
  st_buffer(dist = -0.45)
# mosaic_2 <- mosaic %>%
#   st_buffer(dist = -1)
# mosaic_3 <- mosaic %>%
#   st_buffer(dist = -1.5)

mosaic_1 <- mosaic_1[!st_is_empty(mosaic_1), , drop = FALSE]

# mosaic_2 <- mosaic_2[!st_is_empty(mosaic_2), , drop = FALSE]
# 
# mosaic_3 <- mosaic_3[!st_is_empty(mosaic_3), , drop = FALSE]

Cast to lines (if the buffers are too big a warning is issued and it looks like lines are not retrieved):

mosaic_lines <- mosaic %>%
  st_cast(to = "LINESTRING")

mosaic_lines_1 <- mosaic_1 %>%
  st_cast(to = "LINESTRING")

# mosaic_lines_2 <- mosaic_2 %>%
#   st_cast(to = "LINESTRING")
# 
# mosaic_lines_3 <- mosaic_3 %>%
#   st_cast(to = "LINESTRING")

Plot mosaic:

ggplot() +
  # geom_point(data = marilyn_df,
  #            aes(x, 
  #                y, 
  #                color = value)) + 
  geom_sf(data = mosaic_lines,
          color = "red") +
  geom_sf(data = mosaic_lines_1,
          color = "blue")# +

  # geom_sf(data = mosaic_lines_2,
  #         color = "green") +
  # geom_sf(data = mosaic_lines_3,
  #         color = "black")

Put together

mosaic_lines <- rbind(mosaic_lines %>%
                        mutate(lines = "0"),
                      mosaic_lines_1 %>%
                        mutate(lines = "1"))#,
                      # mosaic_lines_2 %>%
                      #   mutate(lines = "2"),
                      # mosaic_lines_3 %>%
                      #   mutate(lines = "3"))

Plot mosaic:

ggplot() +
  # geom_point(data = marilyn_df,
  #            aes(x, 
  #                y, 
  #                color = value)) + 
  geom_sf(data = mosaic_lines,
          color = "red")

Create a blade:

blade <- data.frame(x_start = c(min(marilyn_df$x):max(marilyn_df$x), 
                                rep(min(marilyn_df$y), 
                                    length(min(marilyn_df$y):max(marilyn_df$y)))),
                    x_end = c(min(marilyn_df$x):max(marilyn_df$x), 
                              rep(max(marilyn_df$x), 
                                  length(min(marilyn_df$y):max(marilyn_df$y)))),
                    y_start = c(rep(min(marilyn_df$y), 
                                    length(min(marilyn_df$x):max(marilyn_df$x))),
                                min(marilyn_df$y):max(marilyn_df$y)),
                    y_end = c(rep(max(marilyn_df$y),
                                  length(min(marilyn_df$x):max(marilyn_df$x))),
                              min(marilyn_df$y):max(marilyn_df$y)))

# Shift the blade a small amount to avoid perfect overlap with underlying grid
blade <- blade %>%
  mutate(across(everything(), 
                ~ .x + 0.28))

blade <- pmap(blade, function(x_start, x_end, y_start, y_end){
  st_linestring(
    matrix(
      c(
        x_start,
        y_start,
        x_end,
        y_end),
      ncol = 2,byrow = TRUE)
  )
}) %>%
  st_as_sfc()

Use the blade to split the lines:

mosaic_lines <- mosaic_lines %>%
  st_split(blade)

Extract the geometries:

mosaic_lines <- mosaic_lines %>%
  st_collection_extract(type = "LINESTRING") %>%
  mutate(id = 1:n())

Convert the data frame with the image to simple features. This way we can use functions from the {sf} package to find the nearest feature to borrow the original colors in the image:

marilyn_sf <- marilyn_df %>%
  st_as_sf(coords = c("x", "y"))

Find the nearest feature and borrow color:

value <- marilyn_sf[mosaic_lines %>% 
                      st_nearest_feature(marilyn_sf),] %>%
  pull(value)

We can now add the hexadecimal colors to the data frame with the mosaic:

mosaic_lines$value <- value

Plot mosaic:

ggplot() +
  # geom_point(data = marilyn_df,
  #            aes(x,
  #                y,
  #                color = value)) +
  geom_sf(data = mosaic_lines,
          aes(size = value,
              color = value)) +
  scale_color_distiller(direction = 1) +
  scale_size(range = c(0.05, 0.75)) + 
  coord_sf(expand = FALSE)

ggsave("junk.png")
Saving 7.29 x 4.51 in image

An alernative

The above does not work super well because there is still too much white space. It works well here because the density of lines is higher (see https://twitter.com/dickie_roper/status/1495166762014412802/photo/1).

So one way to get greater density of lines is to overlap several mosaics.

Load package:

library(imager)
library(purrr)

Read the image using imager::load.image():

marilyn <- load.image("marilyn.jpg")

Image info:

marilyn
Image. Width: 800 pix Height: 1200 pix Depth: 1 Colour channels: 1 

This is the image:

plot(marilyn)

Resize image:

marilyn_rs <- imresize(marilyn, scale = 1/4, interpolation = 6)

Convert to data frame:

marilyn_df <- marilyn_rs %>%
  #grayscale() %>% 
  as.data.frame() %>%
  mutate(y = -(y - max(y)))
ggplot() +
  geom_point(data = marilyn_df,
             aes(x, y, color = value)) +
  coord_equal()

Create data frame for the mosaic:

# This will use a smaller subset of points to create the mosaic, which will then be rescaled
s <- 15

xlim <- c(min(marilyn_df$x)/s - 4, max(marilyn_df$x)/s + 4)
ylim <- c(min(marilyn_df$y)/s - 4, max(marilyn_df$y)/s + 4)

# Create a data frame with the spots for tiles
m_1 <- expand.grid(x = seq(xlim[1], xlim[2], 1),
                      y = seq(ylim[1], ylim[2], 1)) %>%
  mutate(tiles = sample(c("dl", "dr"), n(), replace = TRUE),
         scale_p = 1)

Create mosaic using the designed container:

m_1 <- st_truchet_ms(df = m_1)

Plot mosaic:

ggplot() +
  geom_sf(data = m_1 %>% st_truchet_dissolve(),
          aes(fill = color),
          color = "white")

m_2 <- m_1 %>% st_truchet_dissolve()  %>% st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_2 <- m_2[!st_is_empty(m_2), , drop = FALSE]

m_3 <- m_2 %>% st_truchet_dissolve()  %>% st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_3 <- m_3[!st_is_empty(m_3), , drop = FALSE]
m_1_lines <- m_1 %>% 
  st_truchet_dissolve() %>% 
  st_cast(to = "MULTILINESTRING")
m_2_lines <- m_2 %>% 
  st_cast(to = "MULTILINESTRING")
m_3_lines <- m_3 %>% 
  st_cast(to = "MULTILINESTRING")

Plot:

ggplot() +
  geom_sf(data = m_1_lines,
          color = "red") +
  geom_sf(data = m_2_lines,
          color = "blue") +
  geom_sf(data = m_3_lines,
          color = "black")

Now this needs to be scaled to the size of the image. First get the union of the geometries:

m_1_union <- st_union(m_1)
m_2_union <- st_union(m_2)

Then scale and recenter:

m_1_union <- (m_1_lines * s) %>%
  st_sf()
m_2_union <- (m_2_lines * s) %>% 
  st_sf()
m_3_union <- (m_3_lines * s) %>% 
  st_sf()

Plot mosaic:

ggplot() +
  geom_sf(data = m_1_union,
          color = "red") +
  geom_sf(data = m_2_union,
          color = "blue") +
  geom_sf(data = m_3_union,
          color = "yellow")

Put it all together:

mosaic <- rbind(m_1_union,
                m_2_union,
                m_3_union)

Plot mosaic:

ggplot() +
  geom_sf(data = mosaic,
          aes(color = color))

Create a blade:

bbox <- st_bbox(mosaic) %>% 
  round()

# blade <- data.frame(x_start = c(min(marilyn_df$x):max(marilyn_df$x), 
#                                 rep(min(marilyn_df$y), 
#                                     length(min(marilyn_df$y):max(marilyn_df$y)))),
#                     x_end = c(min(marilyn_df$x):max(marilyn_df$x), 
#                               rep(max(marilyn_df$x), 
#                                   length(min(marilyn_df$y):max(marilyn_df$y)))),
#                     y_start = c(rep(min(marilyn_df$y), 
#                                     length(min(marilyn_df$x):max(marilyn_df$x))),
#                                 min(marilyn_df$y):max(marilyn_df$y)),
#                     y_end = c(rep(max(marilyn_df$y),
#                                   length(min(marilyn_df$x):max(marilyn_df$x))),
#                               min(marilyn_df$y):max(marilyn_df$y)))

blade <- data.frame(x_start = c(bbox$xmin:bbox$xmax, 
                                rep(bbox$ymin, 
                                    length(bbox$ymin:bbox$ymax))),
                    x_end = c(bbox$xmin:bbox$xmax, 
                              rep(bbox$xmax, 
                                  length(bbox$ymin:bbox$ymax))),
                    y_start = c(rep(bbox$ymin, 
                                    length(bbox$xmin:bbox$xmax)),
                                bbox$ymin:bbox$ymax),
                    y_end = c(rep(bbox$ymax,
                                  length(bbox$xmin:bbox$xmax)),
                              bbox$ymin:bbox$ymax))

# Shift the blade a small amount to avoid perfect overlap with underlying grid
blade <- blade %>%
  mutate(across(everything(), 
                ~ .x + 0.28))

blade <- pmap(blade, function(x_start, x_end, y_start, y_end){
  st_linestring(
    matrix(
      c(
        x_start,
        y_start,
        x_end,
        y_end),
      ncol = 2,byrow = TRUE)
  )
}) %>%
  st_as_sfc()

Use the blade to split the lines:

junk <- mosaic %>%
  st_split(blade)

Extract the geometries:

mosaic_lines <- junk %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  mutate(id = 1:n())

Convert the data frame with the image to simple features. This way we can use functions from the {sf} package to find the nearest feature to borrow the original colors in the image:

marilyn_sf <- marilyn_df %>%
  st_as_sf(coords = c("x", "y"))

Find the nearest feature and borrow color:

value <- marilyn_sf[mosaic_lines %>% 
                      st_nearest_feature(marilyn_sf),] %>%
  pull(value)

We can now add the greyscale value to the data frame with the mosaic:

mosaic_lines$value <- value

Plot mosaic:

ggplot() +
  geom_sf(data = mosaic_lines %>%
            st_crop(marilyn_sf),
          aes(color = value,
              size = exp(-3 * value))) +
  scale_color_gradientn(colors = rev(mex.brewer("Frida"))) +
  scale_size(range = c(0.01, 0.80)) + 
  coord_sf(expand = FALSE) + 
  theme_void() + 
  theme(legend.position = "none",
        plot.margin = margin(0.1, 0.1, 0.1, 0.1, "in"),
        panel.background = element_rect(color = NA,
                                        fill = mex.brewer("Frida")[1]),
        plot.background = element_rect(color = NA,
                                        fill = mex.brewer("Frida")[1]))
Warning: attribute variables are assumed to be spatially constant throughout all geometries
ggsave("truchet-marilyn.png",
       height = 7.5,
       width = 5,
       units = "in")

So I think I figured this out. First, we don’t need a super dense set of lines. But we need to split those lines very finely so that they can pick up variations in the grayscale values with higher resolution. Secondly, it helps if the resolution of the underlying image is not too low, otherwise the lines tend to blur the detail.

Next, let’s do a Dali.

Read the image using imager::load.image():

dali <- load.image("dali.jpg")

Image info:

dali
Image. Width: 313 pix Height: 400 pix Depth: 1 Colour channels: 3 

This is the image:

Resize image:

dali_rs <- imresize(dali, scale = 3/8, interpolation = 6)

Convert to data frame:

df <- dali_rs %>%
  #grayscale() %>% 
  as.data.frame() %>%
  mutate(y = -(y - max(y)))

Create data frame for the mosaic:

# This will use a smaller subset of points to create the mosaic, which will then be rescaled
s <- 10

xlim <- c(min(df$x)/s - 4, max(df$x)/s + 4)
ylim <- c(min(df$y)/s - 4, max(df$y)/s + 4)

# Create a data frame with the spots for tiles
m_1 <- expand.grid(x = seq(xlim[1], xlim[2], 1),
                      y = seq(ylim[1], ylim[2], 1)) %>%
  mutate(tiles = sample(c("dl", "dr"), n(), replace = TRUE),
         scale_p = 1)

Create mosaic using the designed container:

m_1 <- st_truchet_ms(df = m_1) %>% 
  st_truchet_dissolve()

Dissolve and buffer:

m_2 <- m_1 %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_2 <- m_2[!st_is_empty(m_2), , drop = FALSE]

m_3 <- m_2 %>% 
  st_truchet_dissolve() %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_3 <- m_3[!st_is_empty(m_3), , drop = FALSE]
m_1_lines <- m_1 %>% 
  st_cast(to = "MULTILINESTRING")
m_2_lines <- m_2 %>% 
  st_cast(to = "MULTILINESTRING")
m_3_lines <- m_3 %>% 
  st_cast(to = "MULTILINESTRING")

Now this needs to be scaled to the size of the image. First get the union of the geometries:

m_1_union <- st_union(m_1)
m_2_union <- st_union(m_2)

Then scale and recenter:

m_1_union <- (m_1_lines * s) %>%
  st_sf()
m_2_union <- (m_2_lines * s) %>% 
  st_sf()
m_3_union <- (m_3_lines * s) %>% 
  st_sf()

Put it all together:

mosaic <- rbind(m_1_union,
                m_2_union,
                m_3_union)

Create a blade:

bbox <- st_bbox(mosaic) %>% 
  round()

blade <- data.frame(x_start = c(bbox$xmin:bbox$xmax, 
                                rep(bbox$ymin, 
                                    length(bbox$ymin:bbox$ymax))),
                    x_end = c(bbox$xmin:bbox$xmax, 
                              rep(bbox$xmax, 
                                  length(bbox$ymin:bbox$ymax))),
                    y_start = c(rep(bbox$ymin, 
                                    length(bbox$xmin:bbox$xmax)),
                                bbox$ymin:bbox$ymax),
                    y_end = c(rep(bbox$ymax,
                                  length(bbox$xmin:bbox$xmax)),
                              bbox$ymin:bbox$ymax))

# Shift the blade a small amount to avoid perfect overlap with underlying grid
blade <- blade %>%
  mutate(across(everything(), 
                ~ .x + 0.28))

blade <- pmap(blade, function(x_start, x_end, y_start, y_end){
  st_linestring(
    matrix(
      c(
        x_start,
        y_start,
        x_end,
        y_end),
      ncol = 2,byrow = TRUE)
  )
}) %>%
  st_as_sfc()

Use the blade to split the lines:

mosaic_lines <- mosaic %>%
  st_split(blade)

Extract the geometries:

mosaic_lines <- mosaic_lines %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  mutate(id = 1:n())

Convert the data frame with the image to simple features. This way we can use functions from the {sf} package to find the nearest feature to borrow the original colors in the image:

df_sf <- df %>%
  st_as_sf(coords = c("x", "y"))

Find the nearest feature and borrow color:

value <- df_sf[mosaic_lines %>% 
                      st_nearest_feature(df_sf),] %>%
  pull(value)

We can now add the greyscale value to the data frame with the mosaic:

mosaic_lines$value <- value

Plot mosaic:

ggplot() +
  geom_sf(data = mosaic_lines %>%
            st_crop(df_sf),
          aes(size = exp(-2 * value)),
          color = "black") +
  scale_color_gradientn(colors = rev(mex.brewer("Revolucion"))) +
  scale_size(range = c(0.01, 0.80)) + 
  coord_sf(expand = FALSE) + 
  theme_void() + 
  theme(legend.position = "none",
        plot.margin = margin(0.1, 0.1, 0.1, 0.1, "in"),
        panel.background = element_rect(color = NA,
                                        fill = mex.brewer("Revolucion")[5]),
        plot.background = element_rect(color = NA,
                                        fill = mex.brewer("Revolucion")[5]))
Warning: attribute variables are assumed to be spatially constant throughout all geometries
ggsave("truchet-dali.png",
       height = 6.5,
       width = 5,
       units = "in")

And now a Julieta Ovalle.

Read the image using imager::load.image():

julieta <- load.image("julieta.jpg")

Image info:

julieta
Image. Width: 960 pix Height: 1280 pix Depth: 1 Colour channels: 3 

This is the image:

plot(julieta)

Resize image:

julieta_rs <- imresize(julieta, scale = 1/8, interpolation = 6)

Convert to data frame:

df <- julieta_rs %>%
  grayscale() %>% 
  as.data.frame() %>%
  mutate(y = -(y - max(y)))

This time, though, we also convert image to a data frame but retrieve the colors:

color_df <- julieta_rs %>%
  as.data.frame(wide="c") %>% 
  # Reverse the y axis
  mutate(y = -(y - max(y)),
         hex_color = rgb(c.1,
                         c.2,
                         c.3))

Create data frame for the mosaic:

# This will use a smaller subset of points to create the mosaic, which will then be rescaled
s <- 10

xlim <- c(min(df$x)/s - 4, max(df$x)/s + 4)
ylim <- c(min(df$y)/s - 4, max(df$y)/s + 4)

# Create a data frame with the spots for tiles
m_1 <- expand.grid(x = seq(xlim[1], xlim[2], 1),
                      y = seq(ylim[1], ylim[2], 1)) %>%
  mutate(tiles = sample(c("dl", "dr"), n(), replace = TRUE),
         scale_p = 1)

Create mosaic using the designed container:

m_1 <- st_truchet_ms(df = m_1) %>% 
  st_truchet_dissolve()

Dissolve and buffer:

m_2 <- m_1 %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_2 <- m_2[!st_is_empty(m_2), , drop = FALSE]

m_3 <- m_2 %>% 
  st_truchet_dissolve() %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_3 <- m_3[!st_is_empty(m_3), , drop = FALSE]
m_1_lines <- m_1 %>% 
  st_cast(to = "MULTILINESTRING")
m_2_lines <- m_2 %>% 
  st_cast(to = "MULTILINESTRING")
m_3_lines <- m_3 %>% 
  st_cast(to = "MULTILINESTRING")

Now this needs to be scaled to the size of the image. First get the union of the geometries:

m_1_union <- st_union(m_1)
m_2_union <- st_union(m_2)

Then scale and recenter:

m_1_union <- (m_1_lines * s) %>%
  st_sf()
m_2_union <- (m_2_lines * s) %>% 
  st_sf()
m_3_union <- (m_3_lines * s) %>% 
  st_sf()

Put it all together:

mosaic <- rbind(m_1_union,
                m_2_union,
                m_3_union)

Create a blade:

bbox <- st_bbox(mosaic) %>% 
  round()

blade <- data.frame(x_start = c(bbox$xmin:bbox$xmax, 
                                rep(bbox$ymin, 
                                    length(bbox$ymin:bbox$ymax))),
                    x_end = c(bbox$xmin:bbox$xmax, 
                              rep(bbox$xmax, 
                                  length(bbox$ymin:bbox$ymax))),
                    y_start = c(rep(bbox$ymin, 
                                    length(bbox$xmin:bbox$xmax)),
                                bbox$ymin:bbox$ymax),
                    y_end = c(rep(bbox$ymax,
                                  length(bbox$xmin:bbox$xmax)),
                              bbox$ymin:bbox$ymax))

# Shift the blade a small amount to avoid perfect overlap with underlying grid
blade <- blade %>%
  mutate(across(everything(), 
                ~ .x + 0.28))

blade <- pmap(blade, function(x_start, x_end, y_start, y_end){
  st_linestring(
    matrix(
      c(
        x_start,
        y_start,
        x_end,
        y_end),
      ncol = 2,byrow = TRUE)
  )
}) %>%
  st_as_sfc()

Use the blade to split the lines:

mosaic_lines <- mosaic %>%
  st_split(blade)

Extract the geometries:

mosaic_lines <- mosaic_lines %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  mutate(id = 1:n())

Convert the data frames with the image to simple features. This way we can use functions from the {sf} package to find the nearest feature to borrow the original colors in the image:

df_sf <- df %>%
  st_as_sf(coords = c("x", "y"))

color_df_sf <- color_df %>%
  st_as_sf(coords = c("x", "y"))

Find the nearest feature and borrow tones of gray and hexadecimal colors:

value <- df_sf[mosaic_lines %>% 
                      st_nearest_feature(df_sf),] %>%
  pull(value)

hex_color <- color_df_sf[mosaic_lines %>% 
                      st_nearest_feature(color_df_sf),] %>%
  pull(hex_color)

We can now add the greyscale values and hexadecimal colors to the data frame with the mosaic:

mosaic_lines$value <- value
mosaic_lines$hex_color <- hex_color

Plot mosaic:

ggplot() +
  geom_sf(data = mosaic_lines %>%
            st_crop(df_sf),
          aes(color = hex_color,
              size = exp(-2 * value))) +
  scale_color_identity() +
  scale_size(range = c(0.01, 0.80)) + 
  coord_sf(expand = FALSE) + 
  theme_void() + 
  theme(legend.position = "none",
        plot.margin = margin(0.1, 0.1, 0.1, 0.1, "in"),
        panel.background = element_rect(color = NA,
                                        fill = mex.brewer("Revolucion")[5]),
        plot.background = element_rect(color = NA,
                                       fill = mex.brewer("Revolucion")[5]))

ggsave("truchet-ovalle.png",
       height = 6.5,
       width = 5,
       units = "in")

Plot mosaic:

ggplot() +
  geom_sf(data = mosaic_lines %>%
            st_crop(df_sf),
          aes(color = hex_color,
              size = exp(-2 * value))) +
  scale_color_identity() +
  scale_size(range = c(0.01, 0.80)) + 
  coord_sf(expand = FALSE) + 
  theme_void() + 
  theme(legend.position = "none",
        plot.margin = margin(0.1, 0.1, 0.1, 0.1, "in"),
        panel.background = element_rect(color = NA,
                                        fill = "white"),
        plot.background = element_rect(color = NA,
                                        fill = "white"))
Warning: attribute variables are assumed to be spatially constant throughout all geometries
ggsave("truchet-ovalle-w.png",
       height = 6.5,
       width = 5,
       units = "in")

A more complex example with mutliple containers

Read the image using imager::load.image():

clouds <- load.image("clouds.jpg")

Image info:

clouds
Image. Width: 2066 pix Height: 2066 pix Depth: 1 Colour channels: 3 
clouds_rs <- imresize(clouds, scale = 1/10, interpolation = 6)
clouds.g <- grayscale(clouds_rs)

Convert to data frame:

df <- clouds_rs %>%
  grayscale() %>% 
  as.data.frame() %>%
  mutate(y = -(y - max(y)))

This time, though, we also convert image to a data frame but retrieve the colors:

color_df <- clouds_rs %>%
  as.data.frame(wide="c") %>% 
  # Reverse the y axis
  mutate(y = -(y - max(y)),
         hex_color = rgb(c.1,
                         c.2,
                         c.3))

Bind grayscale and hexadecimal colors in the same data frame:

df <- cbind(df,
            color_df %>%
              select(hex_color))

Convert the data frame to simple features:

df_sf <- df %>%
  st_as_sf(coords = c("x", "y")) %>%
  cbind(df %>% select(x, y))

Identify paths for blades to split image:

im <- clouds.g

##The following function makes a data.frame of links between pixel (x,y) and pixel (x+dx,y+dy)
##I'm sure there's a better way of doing things
make.df <- function(dx,dy){
  (abs(im-imshift(im,dx,dy))) %>%
    as.data.frame() %>%
    mutate(x.to = x-dx, y.to = y-dy, id.from = paste(x,y,sep=","), id.to = paste(x.to,y.to,sep=",")) %>%
    dplyr::select(id.from, id.to, value) %>%
    dplyr::rename(weight=value)}

# Path 1
## Get all neighbours, convert data.frame to graph
G <- cross2(-1:1,-1:1,function(a,b) abs(a) +abs(b) == 0) %>%
  map_df(lift(function(dx,dy) mutate(make.df(dx,dy),dx=dx,dy=dy))) %>%
  mutate(weight = 1/(weight + 0.01)^7)#exp(-0.05 * weight))

G <- G %>%
  graph_from_data_frame()

#Extract shortest paths
path_1 <- shortest_paths(G,"1,140","258,150") %$% V(G)[vpath[[1]]]%>%
  names %>% stringr::str_split(",") %>%
  map_df(~ data.frame(x=as.integer(.[[1]]),y=as.integer(.[[2]])))

# Path 2
## Get all neighbours, convert data.frame to graph
G <- cross2(-1:1,-1:1,function(a,b) abs(a) +abs(b) == 0) %>%
  map_df(lift(function(dx,dy) mutate(make.df(dx,dy),dx=dx,dy=dy))) %>%
  mutate(weight = 1/(weight + 0.01)^1)#exp(-0.05 * weight))

G <- G %>%
  graph_from_data_frame()

#Extract shortest paths
path_2 <- shortest_paths(G,"1,150","258,150") %$% V(G)[vpath[[1]]]%>%
  names %>% stringr::str_split(",") %>%
  map_df(~ data.frame(x=as.integer(.[[1]]),y=as.integer(.[[2]])))

# Path 3
## Get all neighbours, convert data.frame to graph
G <- cross2(-1:1,-1:1,function(a,b) abs(a) +abs(b) == 0) %>%
  map_df(lift(function(dx,dy) mutate(make.df(dx,dy),dx=dx,dy=dy))) %>%
  mutate(weight = 1/(weight + 0.01)^2)#exp(-0.05 * weight))

G <- G %>%
  graph_from_data_frame()

#Extract shortest paths
path_3 <- shortest_paths(G,"1,205","258,200") %$% V(G)[vpath[[1]]]%>%
  names %>% stringr::str_split(",") %>%
  map_df(~ data.frame(x=as.integer(.[[1]]),y=as.integer(.[[2]])))

Check the paths:

plot(im)
lines(path_1$x,path_1$y,col="green",lty=2,lwd=2)
lines(path_2$x,path_2$y,col="red",lty=2,lwd=2)
lines(path_3$x,path_3$y,col="blue",lty=2,lwd=2)

Reverse the y axis of the paths and shift the y coordinates:

path_1 <- path_1 %>%
  mutate(y = -(y - max(y)) + 107)

path_2 <- path_2 %>%
  mutate(y = -(y - max(y)) + 68)

path_3 <- path_3 %>%
  mutate(y = -(y - max(y)) + 30)

Convert paths to simple features:

# Put together
paths <- rbind(path_1 ,
               path_2,
               path_3) %>%
  # Simplify paths
  st_simplify(dToleracnce = 2)
Error in st_simplify(., dToleracnce = 2) : 
  unused argument (dToleracnce = 2)

Create container:

container <- matrix(c(min(df$x) + 1, min(df$y) + 2,
                      min(df$x) + 1, max(df$y) - 2,
                      max(df$x) - 2, max(df$y) - 2,
                      max(df$x) - 2, min(df$y) + 2,
                      min(df$x) + 1, min(df$y) + 2),
                    ncol = 2,
                    byrow = TRUE)

# Convert coordinates to polygons and then to simple features
container <- data.frame(geometry = sf::st_polygon(list(container)) %>%
                          sf::st_sfc()) %>%
  sf::st_as_sf()

Plot container and paths

ggplot() + 
  # geom_sf(data = df_sf,
  #         aes(color = value)) +
  geom_sf(data  = paths,
          aes(color = path)) + 
  geom_sf(data = container,
          color = "purple",
          fill = NA)

Use the paths to split the container:

container <- container %>%
  st_split(paths) %>%
  st_collection_extract() %>%
  mutate(id = 1:n())

Plot:

ggplot() +
  geom_sf(data = container,
          aes(fill = factor(id)))

Spatial join the data frame with the image. Create buffers of the parts of the cointainer to have some overlap between the mosaics to avoid blanks in the final mosaic:

df_sf <- df_sf %>%
  st_join(container)

df_1_sf <- df_sf %>%
  st_join(container %>%
            filter(id == "1") %>%
            st_buffer(dist = 10))

df_2_sf <- df_sf %>%
  st_join(container %>%
            filter(id == "2") %>%
            st_buffer(dist = 10))

df_3_sf <- df_sf %>%
  st_join(container %>%
            filter(id == "3") %>%
            st_buffer(dist = 10))

df_4_sf <- df_sf %>%
  st_join(container %>%
            filter(id == "4") %>%
            st_buffer(dist = 10))
ggplot() + 
  geom_sf(data = df_1_sf %>% 
         filter(x %% 5 == 0, 
                y %% 5 == 0),
         aes(color = id,
             shape = factor(id)))  + 
  geom_sf(data = df_2_sf %>% 
         filter(x %% 5 == 0, 
                y %% 5 == 0),
         aes(color = id,
             shape = factor(id))) + 
  geom_sf(data = df_3_sf %>% 
         filter(x %% 5 == 0, 
                y %% 5 == 0),
         aes(color = id,
             shape = factor(id))) + 
  geom_sf(data = df_4_sf %>% 
         filter(x %% 5 == 0, 
                y %% 5 == 0),
         aes(color = id,
             shape = factor(id)))
Warning: Removed 2042 rows containing missing values (geom_sf).
Warning: Removed 1964 rows containing missing values (geom_sf).
Warning: Removed 1536 rows containing missing values (geom_sf).
Warning: Removed 1612 rows containing missing values (geom_sf).

Create data frames for the mosaic:

# This will use a smaller subset of points to create the mosaic, which will then be rescaled
s <- 15

# Create a data frame with the spots for tiles
m_4 <- df_4_sf %>%
  filter(id == 4, 
         x %% s == 0, 
         y %% s == 0) %>%
  mutate(x = x/s,
         y = y/s,
         tiles = sample(c("-", "|", "tn"), n(), replace = TRUE),
         scale_p = 1)

m_3 <- df_3_sf %>%
  filter(id == 3, 
         x %% s == 0, 
         y %% s == 0) %>%
  mutate(x = x/s,
         y = y/s,
         tiles = sample(c("fse", "fsw", "+"), n(), replace = TRUE),
         scale_p = 1)

m_2 <- df_2_sf %>%
  filter(id == 2, 
         x %% s == 0, 
         y %% s == 0) %>%
  mutate(x = x/s,
         y = y/s,
         tiles = sample(c("dl", "dr"), n(), replace = TRUE),
         scale_p = 1)

m_1 <- df_1_sf %>%
  filter(id == 1, 
         x %% s == 0, 
         y %% s == 0) %>%
  mutate(x = x/s,
         y = y/s,
         tiles = sample(c("fne", "fnw", "+"), n(), replace = TRUE),
         scale_p = 1)

Create mosaic using the designed container:

# Part 4
m_4 <- st_truchet_ms(df = m_4 %>% 
                       st_drop_geometry()) %>%
  st_truchet_dissolve()

# Part 3
m_3 <- st_truchet_ms(df = m_3 %>% 
                       st_drop_geometry()) %>%
  st_truchet_dissolve()

# Part 2
m_2 <- st_truchet_ms(df = m_2 %>% 
                       st_drop_geometry()) %>%
  st_truchet_dissolve()

# Part 1
m_1 <- st_truchet_ms(df = m_1 %>% 
                       st_drop_geometry()) %>%
  st_truchet_dissolve()

Plot:

ggplot() + 
  geom_sf(data = m_4) + 
  geom_sf(data = m_3) + 
  geom_sf(data = m_2) + 
  geom_sf(data = m_1) 

Dissolve and buffer:

# Container 4
m_4b1 <- m_4 %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_4b1 <- m_4b1[!st_is_empty(m_4b1), , drop = FALSE]

m_4b2 <- m_4b1 %>% 
  st_truchet_dissolve() %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_4b2 <- m_4b2[!st_is_empty(m_4b2), , drop = FALSE]

# Container 3
m_3b1 <- m_3 %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_3b1 <- m_3b1[!st_is_empty(m_3b1), , drop = FALSE]

m_3b2 <- m_3b1 %>% 
  st_truchet_dissolve() %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_3b2 <- m_3b2[!st_is_empty(m_3b2), , drop = FALSE]

# Container 2
m_2b1 <- m_2 %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_2b1 <- m_2b1[!st_is_empty(m_2b1), , drop = FALSE]

m_2b2 <- m_2b1 %>% 
  st_truchet_dissolve() %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_2b2 <- m_2b2[!st_is_empty(m_2b2), , drop = FALSE]

# Container 1
m_1b1 <- m_1 %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_1b1 <- m_1b1[!st_is_empty(m_1b1), , drop = FALSE]

m_1b2 <- m_1b1 %>% 
  st_truchet_dissolve() %>% 
  st_buffer(dist = -0.1) %>%
  mutate(color = color + 2)

m_1b2 <- m_1b2[!st_is_empty(m_1b2), , drop = FALSE]
m_4_lines <- rbind(m_4 %>% 
                     st_cast(to = "MULTILINESTRING"),
                   m_4b1 %>%
                     st_cast(to = "MULTILINESTRING"),
                   m_4b2 %>% 
                     st_cast(to = "MULTILINESTRING"))

m_3_lines <- rbind(m_3 %>% 
                     st_cast(to = "MULTILINESTRING"),
                   m_3b1 %>%
                     st_cast(to = "MULTILINESTRING"),
                   m_3b2 %>% 
                     st_cast(to = "MULTILINESTRING"))

m_2_lines <- rbind(m_2 %>% 
                     st_cast(to = "MULTILINESTRING"),
                   m_2b1 %>%
                     st_cast(to = "MULTILINESTRING"),
                   m_2b2 %>% 
                     st_cast(to = "MULTILINESTRING"))

m_1_lines <- rbind(m_1 %>% 
                     st_cast(to = "MULTILINESTRING"),
                   m_1b1 %>%
                     st_cast(to = "MULTILINESTRING"),
                   m_1b2 %>% 
                     st_cast(to = "MULTILINESTRING"))

Then scale and recenter:

m_4_union <- (m_4_lines * s) %>%
  st_sf()

m_3_union <- (m_3_lines * s) %>% 
  st_sf()

m_2_union <- (m_2_lines * s) %>% 
  st_sf()

m_1_union <- (m_1_lines * s) %>% 
  st_sf()
ggplot() + 
  geom_sf(data = m_4_union,
          color = "blue") + 
  geom_sf(data = m_3_union,
          color = "green") + 
  geom_sf(data = m_2_union,
          color = "red") + 
  geom_sf(data = m_1_union,
          color = "orange")

Put it all together:

mosaic <- rbind(m_1_union,
                m_2_union,
                m_3_union,
                m_4_union)

Create a grid for the blade:

bbox <- st_bbox(mosaic) %>% 
  round()

blade <- data.frame(x_start = c(bbox$xmin:bbox$xmax, 
                                rep(bbox$ymin, 
                                    length(bbox$ymin:bbox$ymax))),
                    x_end = c(bbox$xmin:bbox$xmax, 
                              rep(bbox$xmax, 
                                  length(bbox$ymin:bbox$ymax))),
                    y_start = c(rep(bbox$ymin, 
                                    length(bbox$xmin:bbox$xmax)),
                                bbox$ymin:bbox$ymax),
                    y_end = c(rep(bbox$ymax,
                                  length(bbox$xmin:bbox$xmax)),
                              bbox$ymin:bbox$ymax))

# Shift the blade a small amount to avoid perfect overlap with underlying grid
blade <- blade %>%
  mutate(across(everything(), 
                ~ .x + 0.28))

blade <- pmap(blade, function(x_start, x_end, y_start, y_end){
  st_linestring(
    matrix(
      c(
        x_start,
        y_start,
        x_end,
        y_end),
      ncol = 2,byrow = TRUE)
  )
}) %>%
  st_as_sfc()

Use the blade and the paths to split the lines:

# Part 1
mosaic_lines_1 <- m_1_union %>%
  st_split(blade) %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  st_split(paths) %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  mutate(id = "1")

# Part 2
mosaic_lines_2 <- m_2_union %>%
  st_split(blade) %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  st_split(paths) %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  mutate(id = "2")

# Part 3
mosaic_lines_3 <- m_3_union %>%
  st_split(blade) %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  st_split(paths) %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  mutate(id = "3")

# Part 4
mosaic_lines_4 <- m_4_union %>%
  st_split(blade) %>%
  st_collection_extract(type = "LINESTRING") %>%
  st_cast(to = "LINESTRING") %>%
  mutate(id = "4")

Extract the geometries and select the line segments that are within each of the container polygons:

mosaic_lines_1 <- mosaic_lines_1[container %>% filter(id == "1"),]

mosaic_lines_2 <- mosaic_lines_2[container %>% filter(id == "2"),]

mosaic_lines_3 <- mosaic_lines_3[container %>% filter(id == "3"),]

mosaic_lines_4 <- mosaic_lines_4[container %>% filter(id == "4"),]

Put together:

# mosaic_lines <- rbind(mosaic_lines_1[container %>% filter(id == "1"),],
#                       mosaic_lines_2[container %>% filter(id == "2"),],
#                       mosaic_lines_3[container %>% filter(id == "3"),],
#                       mosaic_lines_4[container %>% filter(id == "4"),])
mosaic_lines <- rbind(mosaic_lines_1,
                      mosaic_lines_2[container %>% filter(id == "2"),],
                      mosaic_lines_3[container %>% filter(id == "3"),],
                      mosaic_lines_4)

Find the nearest feature and borrow tones of gray and hexadecimal colors:

colors_df <- df_sf[mosaic_lines %>% 
                     st_nearest_feature(df_sf),]

We can now add the greyscale values and hexadecimal colors to the data frame with the mosaic:

mosaic_lines$value <- colors_df$value
mosaic_lines$hex_color <- colors_df$hex_color

Create parts of mosaic:

sky_1 <- container %>% filter(id == 4) %>% st_buffer(dist = 3) %>% st_crop(container)
Warning: attribute variables are assumed to be spatially constant throughout all geometries
sky_2 <- container %>% filter(id == 1) %>% st_buffer(dist = 9) %>% st_crop(container)
Warning: attribute variables are assumed to be spatially constant throughout all geometries
cloud <- container %>% filter(id == 2) %>% st_buffer(dist = 9) %>% st_crop(container)
Warning: attribute variables are assumed to be spatially constant throughout all geometries

Plot mosaic (monotone):

ggplot() +
  geom_sf(data = cloud,
          color = NA,
          fill = "white") +
  geom_sf(data = mosaic_lines %>%
            filter(id == 2 | id == 3) %>%
            st_crop(df_sf),
          aes(size = exp(-5 * value),
              color = value)) +
  scale_size(range = c(0.01, 1.3)) + 
  ggnewscale::new_scale("size") +
  geom_sf(data = sky_2,
          color = "black",
          fill = "deepskyblue4") +
  geom_sf(data = mosaic_lines[sky_2,] %>%
            filter(id == "1") %>%
            st_crop(df_sf),
          aes(size = exp(1 * value)),
          color = "white") +
  scale_size(range = c(0.01, 0.3)) +
  geom_sf(data = sky_1, 
          color = "black",
          fill = "goldenrod2") +
  geom_sf(data = mosaic_lines[sky_1,] %>%
            filter(id == "4") %>%
            st_crop(df_sf),
          aes(size = exp(1 * value)),
          color = "white") +
  coord_sf(expand = FALSE) + 
  theme_void() + 
  theme(legend.position = "none",
        plot.margin = margin(0.1, 0.1, 0.1, 0.1, "in"))

ggsave("truchet-clouds-monotone.png",
       height = 7,
       width = 7,
       units = "in")
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKTm9uLXJhbmRvbSBhbGxvY2F0aW9uIG9mIHRpbGVzCmBgYHtyIGxvYWQtcGFja2FnZXMsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkoaW1hZ2VyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGx3Z2VvbSkKbGlicmFyeShNZXhCcmV3ZXIpCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoc2YpCmxpYnJhcnkodHJ1Y2hldCkKYGBgCgpnZ3Bsb3QoKSAgKyBnZW9tX3NmKGRhdGEgPSBtb3NhaWMgJT4lIHN0X2J1ZmZlcihkaXN0ID0gYygwLjE1KSkgJT4lIHN0X3VuaW9uKCksIGNvbG9yID0gImRvZGdlcmJsdWU0IiwgZmlsbCA9ICJkb2RnZXJibHVlMSIsIHNpemUgPSAxKSArIGdlb21fc2YoZGF0YSA9IG1vc2FpYyAlPiUgc3RfYnVmZmVyKGRpc3QgPSBjKDAuMDUpKSwgc2l6ZSA9IDAuNSkKCkNyZWF0ZSBkYXRhIGZyYW1lIGZvciB0aGUgbW9zYWljOgpgYGB7cn0KeGxpbSA8LSBjKDAsIDEwKQp5bGltIDwtIGMoMCwgMTApCgojIENyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCB0aGUgc3BvdHMgZm9yIHRpbGVzCmNvbnRhaW5lciA8LSBleHBhbmQuZ3JpZCh4ID0gc2VxKHhsaW1bMV0sIHhsaW1bMl0sIDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHNlcSh5bGltWzFdLCB5bGltWzJdLCAxKSkgJT4lCiAgbXV0YXRlKHRpbGVzID0gY2FzZV93aGVuKHggPD0gMiB8IHggPj0gOCB+ICJkbCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB4ID4gMiAmIHggPDggfiAiZHIiKSkKYGBgCgpgYGB7cn0Kc3RfdHJ1Y2hldF9zcyhkZiA9IGNvbnRhaW5lcikgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fc2YoKQpgYGAKCkNyZWF0ZSBkYXRhIGZyYW1lIGZvciB0aGUgbW9zYWljOgpgYGB7cn0KeGxpbSA8LSBjKDAsIDEwKQp5bGltIDwtIGMoMCwgMTApCgojIENyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCB0aGUgc3BvdHMgZm9yIHRpbGVzCmNvbnRhaW5lciA8LSBleHBhbmQuZ3JpZCh4ID0gc2VxKHhsaW1bMV0sIHhsaW1bMl0sIDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHNlcSh5bGltWzFdLCB5bGltWzJdLCAxKSkgJT4lCiAgbXV0YXRlKHRpbGVzID0gc2FtcGxlKGMoImRsIiwgImRyIiksIG4oKSwgcmVwbGFjZSA9IFRSVUUpKQpgYGAKCkNyZWF0ZSBtb3NhaWMgdXNpbmcgdGhlIGRlc2lnbmVkIGNvbnRhaW5lcjoKYGBge3J9Cm1vc2FpYyA8LSBzdF90cnVjaGV0X3NzKGRmID0gY29udGFpbmVyKQpgYGAKClBsb3QgbW9zYWljOgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IG1vc2FpYywKICAgICAgICAgIHNpemUgPSAyKQpgYGAKClBsb3Qgd2l0aCBzb21lIGVtYmVsbGlzaG1lbnRzOgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IG1vc2FpYyAlPiUgCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gYygwLjIwKSkgJT4lCiAgICAgICAgICAgIHN0X3VuaW9uKCksIAogICAgICAgICAgY29sb3IgPSAid2hpdGUiLCAKICAgICAgICAgIGZpbGwgPSAiZG9kZ2VyYmx1ZTEiLCAKICAgICAgICAgIHNpemUgPSAwLjUpICsgCiAgZ2VvbV9zZihkYXRhID0gbW9zYWljICU+JSAKICAgICAgICAgICAgc3RfYnVmZmVyKGRpc3QgPSBjKDAuMTUpLAogICAgICAgICAgICAgICAgICAgICAgc2luZ2xlU2lkZSA9IFRSVUUpLAogICAgICAgICAgYWVzKGZpbGwgPSB4ICsgeSksCiAgICAgICAgICBjb2xvciA9IE5BKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzID0gbWV4LmJyZXdlcigiUmV2b2x1Y2lvbiIpKSArCiAgI3NjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiUmRZbEJ1IikgICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yID0gTkEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gImRvZGdlcmJsdWU0IikpCmBgYAoKUGxvdCB3aXRoIHNvbWUgZW1iZWxsaXNobWVudHM6CmBgYHtyfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gbW9zYWljICU+JSAKICAgICAgICAgICAgc3RfYnVmZmVyKGRpc3QgPSBjKDAuMjApKSAlPiUKICAgICAgICAgICAgc3RfdW5pb24oKSwgCiAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIAogICAgICAgICAgZmlsbCA9ICJkb2RnZXJibHVlMSIsIAogICAgICAgICAgc2l6ZSA9IDAuNSkgKyAKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWMsCiAgICAgICAgICBhZXMoY29sb3IgPSBzcXJ0KHheMiArIHleMiksCiAgICAgICAgICAgICAgc2l6ZSA9IHNxcnQoeF4yICsgeV4yKSkpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gbWV4LmJyZXdlcigiQWxhY2VuYSIpKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMC41LCAxLjUpKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9IE5BLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICJkb2RnZXJibHVlNCIpKQpgYGAKCkNyZWF0ZSBhIHBvbHlnb24gdG8gY29udGFpbiB0aGUgbW9zYWljOgpgYGB7cn0KY29udGFpbmVyIDwtIG1hdHJpeChjKDAsIDAsCiAgICAgICAgICAgICAgICAgICAgICAwLCAxMCwKICAgICAgICAgICAgICAgICAgICAgIDEwLCAxMCwKICAgICAgICAgICAgICAgICAgICAgIDEwLCAwLAogICAgICAgICAgICAgICAgICAgICAgMCwgMCksCiAgICAgICAgICAgICAgICAgICAgbmNvbCA9IDIsCiAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKQoKIyBDb252ZXJ0IGNvb3JkaW5hdGVzIHRvIHBvbHlnb25zIGFuZCB0aGVuIHRvIHNpbXBsZSBmZWF0dXJlcwpjb250YWluZXIgPC0gZGF0YS5mcmFtZShnZW9tZXRyeSA9IHNmOjpzdF9wb2x5Z29uKGxpc3QoY29udGFpbmVyKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2Y6OnN0X3NmYygpKSAlPiUKICBzZjo6c3RfYXNfc2YoKQpgYGAKClNwbGl0IGNvbnRhaW5lcgpgYGB7cn0KY29udGFpbmVyIDwtIGNvbnRhaW5lciU+JQogIHN0X3NwbGl0KG1vc2FpYyAlPiUgc3RfdW5pb24oKSkKYGBgCgpFeHRyYWN0IGdlb21ldHJpZXM6CmBgYHtyfQpjb250YWluZXIgPC0gY29udGFpbmVyICU+JSAKICBzdF9jb2xsZWN0aW9uX2V4dHJhY3QoYygiUE9MWUdPTiIpKQpgYGAKClBsb3QgY29udGFpbmVyOgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGNvbnRhaW5lciAlPiUgCiAgICAgICAgICAgIG11dGF0ZShpZCA9IHNhbXBsZSgxOm4oKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuKCkpKSwKICAgICAgICAgIGFlcyhmaWxsID0gaWQpLAogICAgICAgICAgY29sb3IgPSAid2hpdGUiLAogICAgICAgICAgc2l6ZSA9IDAuNSkgKwogIGdlb21fc2YoZGF0YSA9IGNvbnRhaW5lciAlPiUgCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gYygtMC4xNSkpICU+JQogICAgICAgICAgICBtdXRhdGUoaWQgPSBzYW1wbGUoMTpuKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbigpKSksCiAgICAgICAgICBhZXMoZmlsbCA9IGlkKSwKICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgIHNpemUgPSAwLjUpICsKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvcnMgPSBtZXguYnJld2VyKCJSZXZvbHVjaW9uIikpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmdnc2F2ZSgic2luZ2xlLXNjYWxlLXBvbHlnb25zLXJldm9sdWNpb24ucG5nIikKYGBgCgoKU29tZXRoaW5nIGludGVyZXN0aW5nIGhhcHBlbnMgd2l0aCBwb3NpdGl2ZSBidWZmZXJzOgpgYGB7cn0KZ2dwbG90KCkgKwogICMgZ2VvbV9zZihkYXRhID0gY29udGFpbmVyICU+JSAKICAjICAgICAgICAgICBtdXRhdGUoaWQgPSBzYW1wbGUoMTpuKCksIAogICMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuKCkpKSwKICAjICAgICAgICAgYWVzKGZpbGwgPSBpZCksCiAgIyAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAjICAgICAgICAgc2l6ZSA9IDAuNSkgKwogIGdlb21fc2YoZGF0YSA9IGNvbnRhaW5lciAlPiUgCiAgICAgICAgICAgICMgRXhwZXJpbWVudCB3aXRoIGRpZmZlcmVudCBidWZmZXIgc2l6ZXMKICAgICAgICAgICAgc3RfYnVmZmVyKGRpc3QgPSBjKDAuNSkpICU+JQogICAgICAgICAgICBtdXRhdGUoaWQgPSBzYW1wbGUoMTpuKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbigpKSksCiAgICAgICAgICBhZXMoZmlsbCA9IGlkKSwKICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgIHNpemUgPSAxKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzID0gbWV4LmJyZXdlcigiUmV2b2x1Y2lvbiIpKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpnZ3NhdmUoInNpbmdsZS1zY2FsZS1wb2x5Z29ucy1yZXZvbHVjaW9uLXBvc2l0aXZlLWJ1ZmZlcnMtMi5wbmciKQpgYGAKClNvbWV0aGluZyBpbnRlcmVzdGluZyBoYXBwZW5zIHdpdGggcG9zaXRpdmUgYnVmZmVyczoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBjb250YWluZXIgJT4lIAogICAgICAgICAgICAjIEV4cGVyaW1lbnQgd2l0aCBkaWZmZXJlbnQgYnVmZmVyIHNpemVzCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gYygwLjUpKSAlPiUKICAgICAgICAgICAgc3RfaW50ZXJzZWN0aW9uKGNvbnRhaW5lcikgJT4lCiAgICAgICAgICAgIG11dGF0ZShpZCA9IHNhbXBsZSgxOm4oKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuKCkpKSwKICAgICAgICAgIGFlcyhmaWxsID0gaWQpLAogICAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgc2l6ZSA9IDEpICsKICBnZW9tX3NmKGRhdGEgPSBjb250YWluZXIgJT4lIAogICAgICAgICAgICAjIEV4cGVyaW1lbnQgd2l0aCBkaWZmZXJlbnQgYnVmZmVyIHNpemVzCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gYygwLjMpKSAlPiUKICAgICAgICAgICAgc3RfYnVmZmVyKGRpc3QgPSAtMC4yKSAlPiUKICAgICAgICAgICAgbXV0YXRlKGlkID0gc2FtcGxlKDE6bigpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4oKSkpLAogICAgICAgICAgZmlsbCA9IE5BLAogICAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgc2l6ZSA9IDEpICsKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvcnMgPSBtZXguYnJld2VyKCJSZXZvbHVjaW9uIikpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmdnc2F2ZSgic2luZ2xlLXNjYWxlLXBvbHlnb25zLXJldm9sdWNpb24tcG9zaXRpdmUtYnVmZmVycy0zLnBuZyIpCmBgYAoKIyMgVXNpbmcgaW1hZ2VzCgpgYGB7cn0KbGlicmFyeShpbWFnZXIpCmBgYAoKClJlYWQgdGhlIGltYWdlIHVzaW5nIGBpbWFnZXI6OmxvYWQuaW1hZ2UoKWA6CmBgYHtyfQptYXJpbHluIDwtIGxvYWQuaW1hZ2UoIm1hcmlseW4uanBnIikKYGBgCgpJbWFnZSBpbmZvOgpgYGB7cn0KbWFyaWx5bgpgYGAKCmBgYHtyfQpwbG90KG1hcmlseW4pCmBgYAoKUmVzaXplIGltYWdlOgpgYGB7cn0KbWFyaWx5bl9ycyA8LSBpbXJlc2l6ZShtYXJpbHluLCBzY2FsZSA9IDEvOCwgaW50ZXJwb2xhdGlvbiA9IDYpCmBgYAoKQ29udmVydCB0byBkYXRhIGZyYW1lOgpgYGB7cn0KbWFyaWx5bl9kZiA8LSBtYXJpbHluX3JzICU+JQogICNncmF5c2NhbGUoKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIG11dGF0ZSh5ID0gLSh5IC0gbWF4KHkpKSkKYGBgCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IG1hcmlseW5fZGYsCiAgICAgICAgICAgICBhZXMoeCwgeSwgY29sb3IgPSB2YWx1ZSkpICsKICBjb29yZF9lcXVhbCgpCmBgYAoKQ3JlYXRlIGRhdGEgZnJhbWUgZm9yIHRoZSBtb3NhaWM6CmBgYHtyfQp4bGltIDwtIGMobWluKG1hcmlseW5fZGYkeCkvOCAtIDIsIG1heChtYXJpbHluX2RmJHgpLzggKyAyKQp5bGltIDwtIGMobWluKG1hcmlseW5fZGYkeSkvOCAtIDIsIG1heChtYXJpbHluX2RmJHkpLzggKyAyKQoKIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggdGhlIHNwb3RzIGZvciB0aWxlcwptb3NhaWMgPC0gZXhwYW5kLmdyaWQoeCA9IHNlcSh4bGltWzFdLCB4bGltWzJdLCAxKSwKICAgICAgICAgICAgICAgICAgICAgIHkgPSBzZXEoeWxpbVsxXSwgeWxpbVsyXSwgMSkpICU+JQogIG11dGF0ZSh0aWxlcyA9IHNhbXBsZShjKCJkbCIsICJkciIpLCBuKCksIHJlcGxhY2UgPSBUUlVFKSkKYGBgCgpDcmVhdGUgbW9zYWljIHVzaW5nIHRoZSBkZXNpZ25lZCBjb250YWluZXI6CmBgYHtyfQptb3NhaWMgPC0gc3RfdHJ1Y2hldF9zcyhkZiA9IG1vc2FpYykKYGBgCgpQbG90IG1vc2FpYzoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWMsCiAgICAgICAgICBzaXplID0gMSkKYGBgCgpOb3cgdGhpcyBuZWVkcyB0byBiZSBzY2FsZWQgdG8gdGhlIHNpemUgb2YgdGhlIGltYWdlLiBGaXJzdCBnZXQgdGhlIHVuaW9uIG9mIHRoZSBnZW9tZXRyaWVzOgpgYGB7cn0KbW9zYWljX3VuaW9uIDwtIHN0X3VuaW9uKG1vc2FpYykKYGBgCgpUaGVuIHNjYWxlIGFuZCByZWNlbnRlcjoKYGBge3J9Cm1vc2FpY191bmlvbiA8LSBtb3NhaWNfdW5pb24gKiA4IApgYGAKCkNyZWF0ZSBhIHBvbHlnb24gdG8gY29udGFpbiB0aGUgbW9zYWljOgpgYGB7cn0KY29udGFpbmVyIDwtIG1hdHJpeChjKG1pbihtYXJpbHluX2RmJHgpLCBtaW4obWFyaWx5bl9kZiR5KSwKICAgICAgICAgICAgICAgICAgICAgIG1pbihtYXJpbHluX2RmJHgpLCBtYXgobWFyaWx5bl9kZiR5KSwKICAgICAgICAgICAgICAgICAgICAgIG1heChtYXJpbHluX2RmJHgpLCBtYXgobWFyaWx5bl9kZiR5KSwKICAgICAgICAgICAgICAgICAgICAgIG1heChtYXJpbHluX2RmJHgpLCBtaW4obWFyaWx5bl9kZiR5KSwKICAgICAgICAgICAgICAgICAgICAgIG1pbihtYXJpbHluX2RmJHgpLCBtaW4obWFyaWx5bl9kZiR5KSksCiAgICAgICAgICAgICAgICAgICAgbmNvbCA9IDIsCiAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKQoKIyBDb252ZXJ0IGNvb3JkaW5hdGVzIHRvIHBvbHlnb25zIGFuZCB0aGVuIHRvIHNpbXBsZSBmZWF0dXJlcwpjb250YWluZXIgPC0gZGF0YS5mcmFtZShnZW9tZXRyeSA9IHNmOjpzdF9wb2x5Z29uKGxpc3QoY29udGFpbmVyKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2Y6OnN0X3NmYygpKSAlPiUKICBzZjo6c3RfYXNfc2YoKQpgYGAKClNwbGl0IGNvbnRhaW5lcgpgYGB7cn0KbW9zYWljIDwtIGNvbnRhaW5lciAlPiUKICBzdF9zcGxpdChtb3NhaWNfdW5pb24pCmBgYAoKRXh0cmFjdCBnZW9tZXRyaWVzOgpgYGB7cn0KbW9zYWljIDwtIG1vc2FpYyAlPiUgCiAgc3RfY29sbGVjdGlvbl9leHRyYWN0KGMoIlBPTFlHT04iKSkKYGBgCgpDcmVhdGUgYnVmZmVyczoKYGBge3J9Cm1vc2FpY18xIDwtIG1vc2FpYyAlPiUKICBzdF9idWZmZXIoZGlzdCA9IC0xKQoKbW9zYWljXzIgPC0gbW9zYWljICU+JQogIHN0X2J1ZmZlcihkaXN0ID0gLTIpCgptb3NhaWNfMyA8LSBtb3NhaWMgJT4lCiAgc3RfYnVmZmVyKGRpc3QgPSAtMykKYGBgCgoKUGxvdCBtb3NhaWM6CmBgYHtyfQpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gbWFyaWx5bl9kZiwKICAgICAgICAgICAgIGFlcyh4LCB5LCBjb2xvciA9IHZhbHVlKSwKICAgICAgICAgICAgIHNpemUgPSAxLjM1LAogICAgICAgICAgICAgc2hhcGUgPSAxNSkgKwogIGdlb21fc2YoZGF0YSA9IG1vc2FpYywKICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgIGZpbGwgPSBOQSwKICAgICAgICAgIHNpemUgPSAwLjIpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfMSwKICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgIGZpbGwgPSBOQSwKICAgICAgICAgIHNpemUgPSAwLjIpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfMiwKICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgIGZpbGwgPSBOQSwKICAgICAgICAgIHNpemUgPSAwLjIpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfMywKICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgIGZpbGwgPSBOQSwKICAgICAgICAgIHNpemUgPSAwLjIpICsKICBjb29yZF9zZihleHBhbmQgPSBGQUxTRSkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSByZXYobWV4LmJyZXdlcigiQXVyb3JhIikpKSArCiAgI3NjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJibGFjayIsIGhpZ2ggPSAid2hpdGUiKSArCiAgdGhlbWVfdm9pZCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKZ2dzYXZlKCJtYXJpbHluLXRydWNoZXQucG5nIikKYGBgCgojIyBDaGFuZ2luZyB0aGUgd2lkdGggb2YgdGhlIGxpbmVzCgpMb2FkIHBhY2thZ2U6CmBgYHtyfQpsaWJyYXJ5KGltYWdlcikKbGlicmFyeShwdXJycikKYGBgCgoKUmVhZCB0aGUgaW1hZ2UgdXNpbmcgYGltYWdlcjo6bG9hZC5pbWFnZSgpYDoKYGBge3J9Cm1hcmlseW4gPC0gbG9hZC5pbWFnZSgibWFyaWx5bi5qcGciKQpgYGAKCkltYWdlIGluZm86CmBgYHtyfQptYXJpbHluCmBgYAoKYGBge3J9CnBsb3QobWFyaWx5bikKYGBgCgpSZXNpemUgaW1hZ2U6CmBgYHtyfQptYXJpbHluX3JzIDwtIGltcmVzaXplKG1hcmlseW4sIHNjYWxlID0gMS80LCBpbnRlcnBvbGF0aW9uID0gNikKYGBgCgpDb252ZXJ0IHRvIGRhdGEgZnJhbWU6CmBgYHtyfQptYXJpbHluX2RmIDwtIG1hcmlseW5fcnMgJT4lCiAgI2dyYXlzY2FsZSgpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKHkgPSAtKHkgLSBtYXgoeSkpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gbWFyaWx5bl9kZiwKICAgICAgICAgICAgIGFlcyh4LCB5LCBjb2xvciA9IHZhbHVlKSkgKwogIGNvb3JkX2VxdWFsKCkKYGBgCgpDcmVhdGUgZGF0YSBmcmFtZSBmb3IgdGhlIG1vc2FpYzoKYGBge3J9CnMgPC0gOAoKeGxpbSA8LSBjKG1pbihtYXJpbHluX2RmJHgpL3MgLSAyLCBtYXgobWFyaWx5bl9kZiR4KS9zICsgMikKeWxpbSA8LSBjKG1pbihtYXJpbHluX2RmJHkpL3MgLSAyLCBtYXgobWFyaWx5bl9kZiR5KS9zICsgMikKCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSBzcG90cyBmb3IgdGlsZXMKbW9zYWljIDwtIGV4cGFuZC5ncmlkKHggPSBzZXEoeGxpbVsxXSwgeGxpbVsyXSwgMSksCiAgICAgICAgICAgICAgICAgICAgICB5ID0gc2VxKHlsaW1bMV0sIHlsaW1bMl0sIDEpKSAlPiUKICBtdXRhdGUodGlsZXMgPSBzYW1wbGUoYygiZGwiLCAiZHIiKSwgbigpLCByZXBsYWNlID0gVFJVRSkpCmBgYAoKQ3JlYXRlIG1vc2FpYyB1c2luZyB0aGUgZGVzaWduZWQgY29udGFpbmVyOgpgYGB7cn0KbW9zYWljIDwtIHN0X3RydWNoZXRfc3MoZGYgPSBtb3NhaWMpCmBgYAoKUGxvdCBtb3NhaWM6CmBgYHtyfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gbW9zYWljLAogICAgICAgICAgc2l6ZSA9IDEpCmBgYAoKTm93IHRoaXMgbmVlZHMgdG8gYmUgc2NhbGVkIHRvIHRoZSBzaXplIG9mIHRoZSBpbWFnZS4gRmlyc3QgZ2V0IHRoZSB1bmlvbiBvZiB0aGUgZ2VvbWV0cmllczoKYGBge3J9Cm1vc2FpY191bmlvbiA8LSBzdF91bmlvbihtb3NhaWMpCmBgYAoKVGhlbiBzY2FsZSBhbmQgcmVjZW50ZXI6CmBgYHtyfQptb3NhaWNfdW5pb24gPC0gbW9zYWljX3VuaW9uICogcyAKYGBgCgpDcmVhdGUgYSBwb2x5Z29uIHRvIGNvbnRhaW4gdGhlIG1vc2FpYzoKYGBge3J9CmNvbnRhaW5lciA8LSBtYXRyaXgoYyhtaW4obWFyaWx5bl9kZiR4KSwgbWluKG1hcmlseW5fZGYkeSksCiAgICAgICAgICAgICAgICAgICAgICBtaW4obWFyaWx5bl9kZiR4KSwgbWF4KG1hcmlseW5fZGYkeSksCiAgICAgICAgICAgICAgICAgICAgICBtYXgobWFyaWx5bl9kZiR4KSwgbWF4KG1hcmlseW5fZGYkeSksCiAgICAgICAgICAgICAgICAgICAgICBtYXgobWFyaWx5bl9kZiR4KSwgbWluKG1hcmlseW5fZGYkeSksCiAgICAgICAgICAgICAgICAgICAgICBtaW4obWFyaWx5bl9kZiR4KSwgbWluKG1hcmlseW5fZGYkeSkpLAogICAgICAgICAgICAgICAgICAgIG5jb2wgPSAyLAogICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSkKCiMgQ29udmVydCBjb29yZGluYXRlcyB0byBwb2x5Z29ucyBhbmQgdGhlbiB0byBzaW1wbGUgZmVhdHVyZXMKY29udGFpbmVyIDwtIGRhdGEuZnJhbWUoZ2VvbWV0cnkgPSBzZjo6c3RfcG9seWdvbihsaXN0KGNvbnRhaW5lcikpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgIHNmOjpzdF9zZmMoKSkgJT4lCiAgc2Y6OnN0X2FzX3NmKCkKYGBgCgpTcGxpdCBjb250YWluZXIKYGBge3J9Cm1vc2FpYyA8LSBjb250YWluZXIgJT4lCiAgc3Rfc3BsaXQobW9zYWljX3VuaW9uKQpgYGAKCkV4dHJhY3QgZ2VvbWV0cmllczoKYGBge3J9Cm1vc2FpYyA8LSBtb3NhaWMgJT4lIAogIHN0X2NvbGxlY3Rpb25fZXh0cmFjdChjKCJQT0xZR09OIikpCmBgYAoKQ3JlYXRlIGJ1ZmZlcnM6CmBgYHtyfQptb3NhaWNfMSA8LSBtb3NhaWMgJT4lCiAgc3RfYnVmZmVyKGRpc3QgPSAtMC40NSkKIyBtb3NhaWNfMiA8LSBtb3NhaWMgJT4lCiMgICBzdF9idWZmZXIoZGlzdCA9IC0xKQojIG1vc2FpY18zIDwtIG1vc2FpYyAlPiUKIyAgIHN0X2J1ZmZlcihkaXN0ID0gLTEuNSkKCm1vc2FpY18xIDwtIG1vc2FpY18xWyFzdF9pc19lbXB0eShtb3NhaWNfMSksICwgZHJvcCA9IEZBTFNFXQoKIyBtb3NhaWNfMiA8LSBtb3NhaWNfMlshc3RfaXNfZW1wdHkobW9zYWljXzIpLCAsIGRyb3AgPSBGQUxTRV0KIyAKIyBtb3NhaWNfMyA8LSBtb3NhaWNfM1shc3RfaXNfZW1wdHkobW9zYWljXzMpLCAsIGRyb3AgPSBGQUxTRV0KYGBgCgpDYXN0IHRvIGxpbmVzIChpZiB0aGUgYnVmZmVycyBhcmUgdG9vIGJpZyBhIHdhcm5pbmcgaXMgaXNzdWVkIGFuZCBpdCBsb29rcyBsaWtlIGxpbmVzIGFyZSBub3QgcmV0cmlldmVkKToKYGBge3J9Cm1vc2FpY19saW5lcyA8LSBtb3NhaWMgJT4lCiAgc3RfY2FzdCh0byA9ICJMSU5FU1RSSU5HIikKCm1vc2FpY19saW5lc18xIDwtIG1vc2FpY18xICU+JQogIHN0X2Nhc3QodG8gPSAiTElORVNUUklORyIpCgojIG1vc2FpY19saW5lc18yIDwtIG1vc2FpY18yICU+JQojICAgc3RfY2FzdCh0byA9ICJMSU5FU1RSSU5HIikKIyAKIyBtb3NhaWNfbGluZXNfMyA8LSBtb3NhaWNfMyAlPiUKIyAgIHN0X2Nhc3QodG8gPSAiTElORVNUUklORyIpCmBgYAoKUGxvdCBtb3NhaWM6CmBgYHtyfQpnZ3Bsb3QoKSArCiAgIyBnZW9tX3BvaW50KGRhdGEgPSBtYXJpbHluX2RmLAogICMgICAgICAgICAgICBhZXMoeCwgCiAgIyAgICAgICAgICAgICAgICB5LCAKICAjICAgICAgICAgICAgICAgIGNvbG9yID0gdmFsdWUpKSArIAogIGdlb21fc2YoZGF0YSA9IG1vc2FpY19saW5lcywKICAgICAgICAgIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfbGluZXNfMSwKICAgICAgICAgIGNvbG9yID0gImJsdWUiKSMgKwojIGdlb21fc2YoZGF0YSA9IG1vc2FpY19saW5lc18yLAojICAgICAgICAgY29sb3IgPSAiZ3JlZW4iKSArCiMgZ2VvbV9zZihkYXRhID0gbW9zYWljX2xpbmVzXzMsCiMgICAgICAgICBjb2xvciA9ICJibGFjayIpCmBgYAoKUHV0IHRvZ2V0aGVyCmBgYHtyfQptb3NhaWNfbGluZXMgPC0gcmJpbmQobW9zYWljX2xpbmVzICU+JQogICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUobGluZXMgPSAiMCIpLAogICAgICAgICAgICAgICAgICAgICAgbW9zYWljX2xpbmVzXzEgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShsaW5lcyA9ICIxIikpIywKIyBtb3NhaWNfbGluZXNfMiAlPiUKIyAgIG11dGF0ZShsaW5lcyA9ICIyIiksCiMgbW9zYWljX2xpbmVzXzMgJT4lCiMgICBtdXRhdGUobGluZXMgPSAiMyIpKQpgYGAKClBsb3QgbW9zYWljOgpgYGB7cn0KZ2dwbG90KCkgKwogICMgZ2VvbV9wb2ludChkYXRhID0gbWFyaWx5bl9kZiwKICAjICAgICAgICAgICAgYWVzKHgsIAogICMgICAgICAgICAgICAgICAgeSwgCiAgIyAgICAgICAgICAgICAgICBjb2xvciA9IHZhbHVlKSkgKyAKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfbGluZXMsCiAgICAgICAgICBjb2xvciA9ICJyZWQiKQpgYGAKCkNyZWF0ZSBhIGJsYWRlOgpgYGB7cn0KYmxhZGUgPC0gZGF0YS5mcmFtZSh4X3N0YXJ0ID0gYyhtaW4obWFyaWx5bl9kZiR4KTptYXgobWFyaWx5bl9kZiR4KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKG1pbihtYXJpbHluX2RmJHkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKG1pbihtYXJpbHluX2RmJHkpOm1heChtYXJpbHluX2RmJHkpKSkpLAogICAgICAgICAgICAgICAgICAgIHhfZW5kID0gYyhtaW4obWFyaWx5bl9kZiR4KTptYXgobWFyaWx5bl9kZiR4KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcChtYXgobWFyaWx5bl9kZiR4KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgobWluKG1hcmlseW5fZGYkeSk6bWF4KG1hcmlseW5fZGYkeSkpKSksCiAgICAgICAgICAgICAgICAgICAgeV9zdGFydCA9IGMocmVwKG1pbihtYXJpbHluX2RmJHkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKG1pbihtYXJpbHluX2RmJHgpOm1heChtYXJpbHluX2RmJHgpKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluKG1hcmlseW5fZGYkeSk6bWF4KG1hcmlseW5fZGYkeSkpLAogICAgICAgICAgICAgICAgICAgIHlfZW5kID0gYyhyZXAobWF4KG1hcmlseW5fZGYkeSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgobWluKG1hcmlseW5fZGYkeCk6bWF4KG1hcmlseW5fZGYkeCkpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluKG1hcmlseW5fZGYkeSk6bWF4KG1hcmlseW5fZGYkeSkpKQoKIyBTaGlmdCB0aGUgYmxhZGUgYSBzbWFsbCBhbW91bnQgdG8gYXZvaWQgcGVyZmVjdCBvdmVybGFwIHdpdGggdW5kZXJseWluZyBncmlkCmJsYWRlIDwtIGJsYWRlICU+JQogIG11dGF0ZShhY3Jvc3MoZXZlcnl0aGluZygpLCAKICAgICAgICAgICAgICAgIH4gLnggKyAwLjI4KSkKCmJsYWRlIDwtIHBtYXAoYmxhZGUsIGZ1bmN0aW9uKHhfc3RhcnQsIHhfZW5kLCB5X3N0YXJ0LCB5X2VuZCl7CiAgc3RfbGluZXN0cmluZygKICAgIG1hdHJpeCgKICAgICAgYygKICAgICAgICB4X3N0YXJ0LAogICAgICAgIHlfc3RhcnQsCiAgICAgICAgeF9lbmQsCiAgICAgICAgeV9lbmQpLAogICAgICBuY29sID0gMixieXJvdyA9IFRSVUUpCiAgKQp9KSAlPiUKICBzdF9hc19zZmMoKQpgYGAKClVzZSB0aGUgYmxhZGUgdG8gc3BsaXQgdGhlIGxpbmVzOgpgYGB7cn0KbW9zYWljX2xpbmVzIDwtIG1vc2FpY19saW5lcyAlPiUKICBzdF9zcGxpdChibGFkZSkKYGBgCgpFeHRyYWN0IHRoZSBnZW9tZXRyaWVzOgpgYGB7cn0KbW9zYWljX2xpbmVzIDwtIG1vc2FpY19saW5lcyAlPiUKICBzdF9jb2xsZWN0aW9uX2V4dHJhY3QodHlwZSA9ICJMSU5FU1RSSU5HIikgJT4lCiAgbXV0YXRlKGlkID0gMTpuKCkpCmBgYAoKQ29udmVydCB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBpbWFnZSB0byBzaW1wbGUgZmVhdHVyZXMuIFRoaXMgd2F5IHdlIGNhbiB1c2UgZnVuY3Rpb25zIGZyb20gdGhlIHtzZn0gcGFja2FnZSB0byBmaW5kIHRoZSBuZWFyZXN0IGZlYXR1cmUgdG8gYm9ycm93IHRoZSBvcmlnaW5hbCBjb2xvcnMgaW4gdGhlIGltYWdlOgpgYGB7cn0KbWFyaWx5bl9zZiA8LSBtYXJpbHluX2RmICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIngiLCAieSIpKQpgYGAKCkZpbmQgdGhlIG5lYXJlc3QgZmVhdHVyZSBhbmQgYm9ycm93IGNvbG9yOgpgYGB7cn0KdmFsdWUgPC0gbWFyaWx5bl9zZlttb3NhaWNfbGluZXMgJT4lIAogICAgICAgICAgICAgICAgICAgICAgc3RfbmVhcmVzdF9mZWF0dXJlKG1hcmlseW5fc2YpLF0gJT4lCiAgcHVsbCh2YWx1ZSkKYGBgCgpXZSBjYW4gbm93IGFkZCB0aGUgaGV4YWRlY2ltYWwgY29sb3JzIHRvIHRoZSBkYXRhIGZyYW1lIHdpdGggdGhlIG1vc2FpYzoKYGBge3J9Cm1vc2FpY19saW5lcyR2YWx1ZSA8LSB2YWx1ZQpgYGAKClBsb3QgbW9zYWljOgpgYGB7cn0KZ2dwbG90KCkgKwogICMgZ2VvbV9wb2ludChkYXRhID0gbWFyaWx5bl9kZiwKICAjICAgICAgICAgICAgYWVzKHgsCiAgIyAgICAgICAgICAgICAgICB5LAogICMgICAgICAgICAgICAgICAgY29sb3IgPSB2YWx1ZSkpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfbGluZXMsCiAgICAgICAgICBhZXMoc2l6ZSA9IHZhbHVlLAogICAgICAgICAgICAgIGNvbG9yID0gdmFsdWUpKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKGRpcmVjdGlvbiA9IDEpICsKICBzY2FsZV9zaXplKHJhbmdlID0gYygwLjA1LCAwLjc1KSkgKyAKICBjb29yZF9zZihleHBhbmQgPSBGQUxTRSkKCmdnc2F2ZSgianVuay5wbmciKQpgYGAKCgojIyBBbiBhbGVybmF0aXZlCgpUaGUgYWJvdmUgZG9lcyBub3Qgd29yayBzdXBlciB3ZWxsIGJlY2F1c2UgdGhlcmUgaXMgc3RpbGwgdG9vIG11Y2ggd2hpdGUgc3BhY2UuIEl0IHdvcmtzIHdlbGwgaGVyZSBiZWNhdXNlIHRoZSBkZW5zaXR5IG9mIGxpbmVzIGlzIGhpZ2hlciAoc2VlIGh0dHBzOi8vdHdpdHRlci5jb20vZGlja2llX3JvcGVyL3N0YXR1cy8xNDk1MTY2NzYyMDE0NDEyODAyL3Bob3RvLzEpLgoKU28gb25lIHdheSB0byBnZXQgZ3JlYXRlciBkZW5zaXR5IG9mIGxpbmVzIGlzIHRvIG92ZXJsYXAgc2V2ZXJhbCBtb3NhaWNzLgoKTG9hZCBwYWNrYWdlOgpgYGB7cn0KbGlicmFyeShpbWFnZXIpCmxpYnJhcnkocHVycnIpCmBgYAoKUmVhZCB0aGUgaW1hZ2UgdXNpbmcgYGltYWdlcjo6bG9hZC5pbWFnZSgpYDoKYGBge3J9Cm1hcmlseW4gPC0gbG9hZC5pbWFnZSgibWFyaWx5bi5qcGciKQpgYGAKCkltYWdlIGluZm86CmBgYHtyfQptYXJpbHluCmBgYAoKVGhpcyBpcyB0aGUgaW1hZ2U6CmBgYHtyfQpwbG90KG1hcmlseW4pCmBgYAoKUmVzaXplIGltYWdlOgpgYGB7cn0KbWFyaWx5bl9ycyA8LSBpbXJlc2l6ZShtYXJpbHluLCBzY2FsZSA9IDEvNCwgaW50ZXJwb2xhdGlvbiA9IDYpCmBgYAoKQ29udmVydCB0byBkYXRhIGZyYW1lOgpgYGB7cn0KbWFyaWx5bl9kZiA8LSBtYXJpbHluX3JzICU+JQogICNncmF5c2NhbGUoKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIG11dGF0ZSh5ID0gLSh5IC0gbWF4KHkpKSkKYGBgCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IG1hcmlseW5fZGYsCiAgICAgICAgICAgICBhZXMoeCwgeSwgY29sb3IgPSB2YWx1ZSkpICsKICBjb29yZF9lcXVhbCgpCmBgYAoKQ3JlYXRlIGRhdGEgZnJhbWUgZm9yIHRoZSBtb3NhaWM6CmBgYHtyfQojIFRoaXMgd2lsbCB1c2UgYSBzbWFsbGVyIHN1YnNldCBvZiBwb2ludHMgdG8gY3JlYXRlIHRoZSBtb3NhaWMsIHdoaWNoIHdpbGwgdGhlbiBiZSByZXNjYWxlZApzIDwtIDE1Cgp4bGltIDwtIGMobWluKG1hcmlseW5fZGYkeCkvcyAtIDQsIG1heChtYXJpbHluX2RmJHgpL3MgKyA0KQp5bGltIDwtIGMobWluKG1hcmlseW5fZGYkeSkvcyAtIDQsIG1heChtYXJpbHluX2RmJHkpL3MgKyA0KQoKIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggdGhlIHNwb3RzIGZvciB0aWxlcwptXzEgPC0gZXhwYW5kLmdyaWQoeCA9IHNlcSh4bGltWzFdLCB4bGltWzJdLCAxKSwKICAgICAgICAgICAgICAgICAgIHkgPSBzZXEoeWxpbVsxXSwgeWxpbVsyXSwgMSkpICU+JQogIG11dGF0ZSh0aWxlcyA9IHNhbXBsZShjKCJkbCIsICJkciIpLCBuKCksIHJlcGxhY2UgPSBUUlVFKSwKICAgICAgICAgc2NhbGVfcCA9IDEpCmBgYAoKQ3JlYXRlIG1vc2FpYyB1c2luZyB0aGUgZGVzaWduZWQgY29udGFpbmVyOgpgYGB7cn0KbV8xIDwtIHN0X3RydWNoZXRfbXMoZGYgPSBtXzEpCmBgYAoKUGxvdCBtb3NhaWM6CmBgYHtyfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gbV8xICU+JSBzdF90cnVjaGV0X2Rpc3NvbHZlKCksCiAgICAgICAgICBhZXMoZmlsbCA9IGNvbG9yKSwKICAgICAgICAgIGNvbG9yID0gIndoaXRlIikKYGBgCgoKYGBge3J9Cm1fMiA8LSBtXzEgJT4lIHN0X3RydWNoZXRfZGlzc29sdmUoKSAgJT4lIHN0X2J1ZmZlcihkaXN0ID0gLTAuMSkgJT4lCiAgbXV0YXRlKGNvbG9yID0gY29sb3IgKyAyKQoKbV8yIDwtIG1fMlshc3RfaXNfZW1wdHkobV8yKSwgLCBkcm9wID0gRkFMU0VdCgptXzMgPC0gbV8yICU+JSBzdF90cnVjaGV0X2Rpc3NvbHZlKCkgICU+JSBzdF9idWZmZXIoZGlzdCA9IC0wLjEpICU+JQogIG11dGF0ZShjb2xvciA9IGNvbG9yICsgMikKCm1fMyA8LSBtXzNbIXN0X2lzX2VtcHR5KG1fMyksICwgZHJvcCA9IEZBTFNFXQpgYGAKCmBgYHtyfQptXzFfbGluZXMgPC0gbV8xICU+JSAKICBzdF90cnVjaGV0X2Rpc3NvbHZlKCkgJT4lIAogIHN0X2Nhc3QodG8gPSAiTVVMVElMSU5FU1RSSU5HIikKbV8yX2xpbmVzIDwtIG1fMiAlPiUgCiAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKQptXzNfbGluZXMgPC0gbV8zICU+JSAKICBzdF9jYXN0KHRvID0gIk1VTFRJTElORVNUUklORyIpCmBgYAoKUGxvdDoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBtXzFfbGluZXMsCiAgICAgICAgICBjb2xvciA9ICJyZWQiKSArCiAgZ2VvbV9zZihkYXRhID0gbV8yX2xpbmVzLAogICAgICAgICAgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3NmKGRhdGEgPSBtXzNfbGluZXMsCiAgICAgICAgICBjb2xvciA9ICJibGFjayIpCgpgYGAKCk5vdyB0aGlzIG5lZWRzIHRvIGJlIHNjYWxlZCB0byB0aGUgc2l6ZSBvZiB0aGUgaW1hZ2UuIEZpcnN0IGdldCB0aGUgdW5pb24gb2YgdGhlIGdlb21ldHJpZXM6CmBgYHtyfQptXzFfdW5pb24gPC0gc3RfdW5pb24obV8xKQptXzJfdW5pb24gPC0gc3RfdW5pb24obV8yKQpgYGAKClRoZW4gc2NhbGUgYW5kIHJlY2VudGVyOgpgYGB7cn0KbV8xX3VuaW9uIDwtIChtXzFfbGluZXMgKiBzKSAlPiUKICBzdF9zZigpCm1fMl91bmlvbiA8LSAobV8yX2xpbmVzICogcykgJT4lIAogIHN0X3NmKCkKbV8zX3VuaW9uIDwtIChtXzNfbGluZXMgKiBzKSAlPiUgCiAgc3Rfc2YoKQpgYGAKClBsb3QgbW9zYWljOgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IG1fMV91bmlvbiwKICAgICAgICAgIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3NmKGRhdGEgPSBtXzJfdW5pb24sCiAgICAgICAgICBjb2xvciA9ICJibHVlIikgKwogIGdlb21fc2YoZGF0YSA9IG1fM191bmlvbiwKICAgICAgICAgIGNvbG9yID0gInllbGxvdyIpCmBgYAoKUHV0IGl0IGFsbCB0b2dldGhlcjoKYGBge3J9Cm1vc2FpYyA8LSByYmluZChtXzFfdW5pb24sCiAgICAgICAgICAgICAgICBtXzJfdW5pb24sCiAgICAgICAgICAgICAgICBtXzNfdW5pb24pCmBgYAoKUGxvdCBtb3NhaWM6CmBgYHtyfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gbW9zYWljLAogICAgICAgICAgYWVzKGNvbG9yID0gY29sb3IpKQpgYGAKCkNyZWF0ZSBhIGJsYWRlOgpgYGB7cn0KYmJveCA8LSBzdF9iYm94KG1vc2FpYykgJT4lIAogIHJvdW5kKCkKCmJsYWRlIDwtIGRhdGEuZnJhbWUoeF9zdGFydCA9IGMoYmJveCR4bWluOmJib3gkeG1heCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKGJib3gkeW1pbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChiYm94JHltaW46YmJveCR5bWF4KSkpLAogICAgICAgICAgICAgICAgICAgIHhfZW5kID0gYyhiYm94JHhtaW46YmJveCR4bWF4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKGJib3gkeG1heCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoYmJveCR5bWluOmJib3gkeW1heCkpKSwKICAgICAgICAgICAgICAgICAgICB5X3N0YXJ0ID0gYyhyZXAoYmJveCR5bWluLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKGJib3gkeG1pbjpiYm94JHhtYXgpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYm94JHltaW46YmJveCR5bWF4KSwKICAgICAgICAgICAgICAgICAgICB5X2VuZCA9IGMocmVwKGJib3gkeW1heCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChiYm94JHhtaW46YmJveCR4bWF4KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJib3gkeW1pbjpiYm94JHltYXgpKQoKIyBTaGlmdCB0aGUgYmxhZGUgYSBzbWFsbCBhbW91bnQgdG8gYXZvaWQgcGVyZmVjdCBvdmVybGFwIHdpdGggdW5kZXJseWluZyBncmlkCmJsYWRlIDwtIGJsYWRlICU+JQogIG11dGF0ZShhY3Jvc3MoZXZlcnl0aGluZygpLCAKICAgICAgICAgICAgICAgIH4gLnggKyAwLjI4KSkKCmJsYWRlIDwtIHBtYXAoYmxhZGUsIGZ1bmN0aW9uKHhfc3RhcnQsIHhfZW5kLCB5X3N0YXJ0LCB5X2VuZCl7CiAgc3RfbGluZXN0cmluZygKICAgIG1hdHJpeCgKICAgICAgYygKICAgICAgICB4X3N0YXJ0LAogICAgICAgIHlfc3RhcnQsCiAgICAgICAgeF9lbmQsCiAgICAgICAgeV9lbmQpLAogICAgICBuY29sID0gMixieXJvdyA9IFRSVUUpCiAgKQp9KSAlPiUKICBzdF9hc19zZmMoKQpgYGAKClVzZSB0aGUgYmxhZGUgdG8gc3BsaXQgdGhlIGxpbmVzOgpgYGB7cn0KbW9zYWljX2xpbmVzIDwtIG1vc2FpYyAlPiUKICBzdF9zcGxpdChibGFkZSkKYGBgCgpFeHRyYWN0IHRoZSBnZW9tZXRyaWVzOgpgYGB7cn0KbW9zYWljX2xpbmVzIDwtIG1vc2FpY19saW5lcyAlPiUKICBzdF9jb2xsZWN0aW9uX2V4dHJhY3QodHlwZSA9ICJMSU5FU1RSSU5HIikgJT4lCiAgc3RfY2FzdCh0byA9ICJMSU5FU1RSSU5HIikgJT4lCiAgbXV0YXRlKGlkID0gMTpuKCkpCmBgYAoKQ29udmVydCB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBpbWFnZSB0byBzaW1wbGUgZmVhdHVyZXMuIFRoaXMgd2F5IHdlIGNhbiB1c2UgZnVuY3Rpb25zIGZyb20gdGhlIHtzZn0gcGFja2FnZSB0byBmaW5kIHRoZSBuZWFyZXN0IGZlYXR1cmUgdG8gYm9ycm93IHRoZSBvcmlnaW5hbCBjb2xvcnMgaW4gdGhlIGltYWdlOgpgYGB7cn0KbWFyaWx5bl9zZiA8LSBtYXJpbHluX2RmICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIngiLCAieSIpKQpgYGAKCkZpbmQgdGhlIG5lYXJlc3QgZmVhdHVyZSBhbmQgYm9ycm93IGNvbG9yOgpgYGB7cn0KdmFsdWUgPC0gbWFyaWx5bl9zZlttb3NhaWNfbGluZXMgJT4lIAogICAgICAgICAgICAgICAgICAgICAgc3RfbmVhcmVzdF9mZWF0dXJlKG1hcmlseW5fc2YpLF0gJT4lCiAgcHVsbCh2YWx1ZSkKYGBgCgpXZSBjYW4gbm93IGFkZCB0aGUgZ3JleXNjYWxlIHZhbHVlIHRvIHRoZSBkYXRhIGZyYW1lIHdpdGggdGhlIG1vc2FpYzoKYGBge3J9Cm1vc2FpY19saW5lcyR2YWx1ZSA8LSB2YWx1ZQpgYGAKClBsb3QgbW9zYWljOgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IG1vc2FpY19saW5lcyAlPiUKICAgICAgICAgICAgc3RfY3JvcChtYXJpbHluX3NmKSwKICAgICAgICAgIGFlcyhjb2xvciA9IHZhbHVlLAogICAgICAgICAgICAgIHNpemUgPSBleHAoLTMgKiB2YWx1ZSkpKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHJldihtZXguYnJld2VyKCJGcmlkYSIpKSkgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDAuMDEsIDAuODApKSArIAogIGNvb3JkX3NmKGV4cGFuZCA9IEZBTFNFKSArIAogIHRoZW1lX3ZvaWQoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigwLjEsIDAuMSwgMC4xLCAwLjEsICJpbiIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBtZXguYnJld2VyKCJGcmlkYSIpWzFdKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IG1leC5icmV3ZXIoIkZyaWRhIilbMV0pKQoKZ2dzYXZlKCJ0cnVjaGV0LW1hcmlseW4ucG5nIiwKICAgICAgIGhlaWdodCA9IDcuNSwKICAgICAgIHdpZHRoID0gNSwKICAgICAgIHVuaXRzID0gImluIikKYGBgCgpTbyBJIHRoaW5rIEkgZmlndXJlZCB0aGlzIG91dC4gRmlyc3QsIHdlIGRvbid0IG5lZWQgYSBzdXBlciBkZW5zZSBzZXQgb2YgbGluZXMuIEJ1dCB3ZSBuZWVkIHRvIHNwbGl0IHRob3NlIGxpbmVzIHZlcnkgZmluZWx5IHNvIHRoYXQgdGhleSBjYW4gcGljayB1cCB2YXJpYXRpb25zIGluIHRoZSBncmF5c2NhbGUgdmFsdWVzIHdpdGggaGlnaGVyIHJlc29sdXRpb24uIFNlY29uZGx5LCBpdCBoZWxwcyBpZiB0aGUgcmVzb2x1dGlvbiBvZiB0aGUgdW5kZXJseWluZyBpbWFnZSBpcyBub3QgdG9vIGxvdywgb3RoZXJ3aXNlIHRoZSBsaW5lcyB0ZW5kIHRvIGJsdXIgdGhlIGRldGFpbC4KCiMjIE5leHQsIGxldCdzIGRvIGEgRGFsaS4KClJlYWQgdGhlIGltYWdlIHVzaW5nIGBpbWFnZXI6OmxvYWQuaW1hZ2UoKWA6CmBgYHtyfQpkYWxpIDwtIGxvYWQuaW1hZ2UoImRhbGkuanBnIikKYGBgCgpJbWFnZSBpbmZvOgpgYGB7cn0KZGFsaQpgYGAKClRoaXMgaXMgdGhlIGltYWdlOgpgYGB7cn0KcGxvdChkYWxpKQpgYGAKClJlc2l6ZSBpbWFnZToKYGBge3J9CmRhbGlfcnMgPC0gaW1yZXNpemUoZGFsaSwgc2NhbGUgPSAzLzgsIGludGVycG9sYXRpb24gPSA2KQpgYGAKCkNvbnZlcnQgdG8gZGF0YSBmcmFtZToKYGBge3J9CmRmIDwtIGRhbGlfcnMgJT4lCiAgI2dyYXlzY2FsZSgpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKHkgPSAtKHkgLSBtYXgoeSkpKQpgYGAKCkNyZWF0ZSBkYXRhIGZyYW1lIGZvciB0aGUgbW9zYWljOgpgYGB7cn0KIyBUaGlzIHdpbGwgdXNlIGEgc21hbGxlciBzdWJzZXQgb2YgcG9pbnRzIHRvIGNyZWF0ZSB0aGUgbW9zYWljLCB3aGljaCB3aWxsIHRoZW4gYmUgcmVzY2FsZWQKcyA8LSAxMAoKeGxpbSA8LSBjKG1pbihkZiR4KS9zIC0gNCwgbWF4KGRmJHgpL3MgKyA0KQp5bGltIDwtIGMobWluKGRmJHkpL3MgLSA0LCBtYXgoZGYkeSkvcyArIDQpCgojIENyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCB0aGUgc3BvdHMgZm9yIHRpbGVzCm1fMSA8LSBleHBhbmQuZ3JpZCh4ID0gc2VxKHhsaW1bMV0sIHhsaW1bMl0sIDEpLAogICAgICAgICAgICAgICAgICAgeSA9IHNlcSh5bGltWzFdLCB5bGltWzJdLCAxKSkgJT4lCiAgbXV0YXRlKHRpbGVzID0gc2FtcGxlKGMoImRsIiwgImRyIiksIG4oKSwgcmVwbGFjZSA9IFRSVUUpLAogICAgICAgICBzY2FsZV9wID0gMSkKYGBgCgpDcmVhdGUgbW9zYWljIHVzaW5nIHRoZSBkZXNpZ25lZCBjb250YWluZXI6CmBgYHtyfQptXzEgPC0gc3RfdHJ1Y2hldF9tcyhkZiA9IG1fMSkgJT4lIAogIHN0X3RydWNoZXRfZGlzc29sdmUoKQpgYGAKCkRpc3NvbHZlIGFuZCBidWZmZXI6CmBgYHtyfQptXzIgPC0gbV8xICU+JSAKICBzdF9idWZmZXIoZGlzdCA9IC0wLjEpICU+JQogIG11dGF0ZShjb2xvciA9IGNvbG9yICsgMikKCm1fMiA8LSBtXzJbIXN0X2lzX2VtcHR5KG1fMiksICwgZHJvcCA9IEZBTFNFXQoKbV8zIDwtIG1fMiAlPiUgCiAgc3RfdHJ1Y2hldF9kaXNzb2x2ZSgpICU+JSAKICBzdF9idWZmZXIoZGlzdCA9IC0wLjEpICU+JQogIG11dGF0ZShjb2xvciA9IGNvbG9yICsgMikKCm1fMyA8LSBtXzNbIXN0X2lzX2VtcHR5KG1fMyksICwgZHJvcCA9IEZBTFNFXQpgYGAKCmBgYHtyfQptXzFfbGluZXMgPC0gbV8xICU+JSAKICBzdF9jYXN0KHRvID0gIk1VTFRJTElORVNUUklORyIpCm1fMl9saW5lcyA8LSBtXzIgJT4lIAogIHN0X2Nhc3QodG8gPSAiTVVMVElMSU5FU1RSSU5HIikKbV8zX2xpbmVzIDwtIG1fMyAlPiUgCiAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKQpgYGAKCgpOb3cgdGhpcyBuZWVkcyB0byBiZSBzY2FsZWQgdG8gdGhlIHNpemUgb2YgdGhlIGltYWdlLiBGaXJzdCBnZXQgdGhlIHVuaW9uIG9mIHRoZSBnZW9tZXRyaWVzOgpgYGB7cn0KbV8xX3VuaW9uIDwtIHN0X3VuaW9uKG1fMSkKbV8yX3VuaW9uIDwtIHN0X3VuaW9uKG1fMikKYGBgCgpUaGVuIHNjYWxlIGFuZCByZWNlbnRlcjoKYGBge3J9Cm1fMV91bmlvbiA8LSAobV8xX2xpbmVzICogcykgJT4lCiAgc3Rfc2YoKQptXzJfdW5pb24gPC0gKG1fMl9saW5lcyAqIHMpICU+JSAKICBzdF9zZigpCm1fM191bmlvbiA8LSAobV8zX2xpbmVzICogcykgJT4lIAogIHN0X3NmKCkKYGBgCgpQdXQgaXQgYWxsIHRvZ2V0aGVyOgpgYGB7cn0KbW9zYWljIDwtIHJiaW5kKG1fMV91bmlvbiwKICAgICAgICAgICAgICAgIG1fMl91bmlvbiwKICAgICAgICAgICAgICAgIG1fM191bmlvbikKYGBgCgpDcmVhdGUgYSBibGFkZToKYGBge3J9CmJib3ggPC0gc3RfYmJveChtb3NhaWMpICU+JSAKICByb3VuZCgpCgpibGFkZSA8LSBkYXRhLmZyYW1lKHhfc3RhcnQgPSBjKGJib3gkeG1pbjpiYm94JHhtYXgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcChiYm94JHltaW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoYmJveCR5bWluOmJib3gkeW1heCkpKSwKICAgICAgICAgICAgICAgICAgICB4X2VuZCA9IGMoYmJveCR4bWluOmJib3gkeG1heCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcChiYm94JHhtYXgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKGJib3gkeW1pbjpiYm94JHltYXgpKSksCiAgICAgICAgICAgICAgICAgICAgeV9zdGFydCA9IGMocmVwKGJib3gkeW1pbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChiYm94JHhtaW46YmJveCR4bWF4KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmJveCR5bWluOmJib3gkeW1heCksCiAgICAgICAgICAgICAgICAgICAgeV9lbmQgPSBjKHJlcChiYm94JHltYXgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoYmJveCR4bWluOmJib3gkeG1heCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYm94JHltaW46YmJveCR5bWF4KSkKCiMgU2hpZnQgdGhlIGJsYWRlIGEgc21hbGwgYW1vdW50IHRvIGF2b2lkIHBlcmZlY3Qgb3ZlcmxhcCB3aXRoIHVuZGVybHlpbmcgZ3JpZApibGFkZSA8LSBibGFkZSAlPiUKICBtdXRhdGUoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgCiAgICAgICAgICAgICAgICB+IC54ICsgMC4yOCkpCgpibGFkZSA8LSBwbWFwKGJsYWRlLCBmdW5jdGlvbih4X3N0YXJ0LCB4X2VuZCwgeV9zdGFydCwgeV9lbmQpewogIHN0X2xpbmVzdHJpbmcoCiAgICBtYXRyaXgoCiAgICAgIGMoCiAgICAgICAgeF9zdGFydCwKICAgICAgICB5X3N0YXJ0LAogICAgICAgIHhfZW5kLAogICAgICAgIHlfZW5kKSwKICAgICAgbmNvbCA9IDIsYnlyb3cgPSBUUlVFKQogICkKfSkgJT4lCiAgc3RfYXNfc2ZjKCkKYGBgCgpVc2UgdGhlIGJsYWRlIHRvIHNwbGl0IHRoZSBsaW5lczoKYGBge3J9Cm1vc2FpY19saW5lcyA8LSBtb3NhaWMgJT4lCiAgc3Rfc3BsaXQoYmxhZGUpCmBgYAoKRXh0cmFjdCB0aGUgZ2VvbWV0cmllczoKYGBge3J9Cm1vc2FpY19saW5lcyA8LSBtb3NhaWNfbGluZXMgJT4lCiAgc3RfY29sbGVjdGlvbl9leHRyYWN0KHR5cGUgPSAiTElORVNUUklORyIpICU+JQogIHN0X2Nhc3QodG8gPSAiTElORVNUUklORyIpICU+JQogIG11dGF0ZShpZCA9IDE6bigpKQpgYGAKCkNvbnZlcnQgdGhlIGRhdGEgZnJhbWUgd2l0aCB0aGUgaW1hZ2UgdG8gc2ltcGxlIGZlYXR1cmVzLiBUaGlzIHdheSB3ZSBjYW4gdXNlIGZ1bmN0aW9ucyBmcm9tIHRoZSB7c2Z9IHBhY2thZ2UgdG8gZmluZCB0aGUgbmVhcmVzdCBmZWF0dXJlIHRvIGJvcnJvdyB0aGUgb3JpZ2luYWwgY29sb3JzIGluIHRoZSBpbWFnZToKYGBge3J9CmRmX3NmIDwtIGRmICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIngiLCAieSIpKQpgYGAKCkZpbmQgdGhlIG5lYXJlc3QgZmVhdHVyZSBhbmQgYm9ycm93IGNvbG9yOgpgYGB7cn0KdmFsdWUgPC0gZGZfc2ZbbW9zYWljX2xpbmVzICU+JSAKICAgICAgICAgICAgICAgICBzdF9uZWFyZXN0X2ZlYXR1cmUoZGZfc2YpLF0gJT4lCiAgcHVsbCh2YWx1ZSkKYGBgCgpXZSBjYW4gbm93IGFkZCB0aGUgZ3JleXNjYWxlIHZhbHVlIHRvIHRoZSBkYXRhIGZyYW1lIHdpdGggdGhlIG1vc2FpYzoKYGBge3J9Cm1vc2FpY19saW5lcyR2YWx1ZSA8LSB2YWx1ZQpgYGAKClBsb3QgbW9zYWljOgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IG1vc2FpY19saW5lcyAlPiUKICAgICAgICAgICAgc3RfY3JvcChkZl9zZiksCiAgICAgICAgICBhZXMoc2l6ZSA9IGV4cCgtMiAqIHZhbHVlKSksCiAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gcmV2KG1leC5icmV3ZXIoIlJldm9sdWNpb24iKSkpICsKICBzY2FsZV9zaXplKHJhbmdlID0gYygwLjAxLCAwLjgwKSkgKyAKICBjb29yZF9zZihleHBhbmQgPSBGQUxTRSkgKyAKICB0aGVtZV92b2lkKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMC4xLCAwLjEsIDAuMSwgMC4xLCAiaW4iKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yID0gTkEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gbWV4LmJyZXdlcigiUmV2b2x1Y2lvbiIpWzVdKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IG1leC5icmV3ZXIoIlJldm9sdWNpb24iKVs1XSkpCgpnZ3NhdmUoInRydWNoZXQtZGFsaS5wbmciLAogICAgICAgaGVpZ2h0ID0gNi41LAogICAgICAgd2lkdGggPSA1LAogICAgICAgdW5pdHMgPSAiaW4iKQpgYGAKCiMjIEFuZCBub3cgYSBKdWxpZXRhIE92YWxsZS4KClJlYWQgdGhlIGltYWdlIHVzaW5nIGBpbWFnZXI6OmxvYWQuaW1hZ2UoKWA6CmBgYHtyfQpqdWxpZXRhIDwtIGxvYWQuaW1hZ2UoImp1bGlldGEuanBnIikKYGBgCgpJbWFnZSBpbmZvOgpgYGB7cn0KanVsaWV0YQpgYGAKClRoaXMgaXMgdGhlIGltYWdlOgpgYGB7cn0KcGxvdChqdWxpZXRhKQpgYGAKClJlc2l6ZSBpbWFnZToKYGBge3J9Cmp1bGlldGFfcnMgPC0gaW1yZXNpemUoanVsaWV0YSwgc2NhbGUgPSAxLzgsIGludGVycG9sYXRpb24gPSA2KQpgYGAKCkNvbnZlcnQgdG8gZGF0YSBmcmFtZToKYGBge3J9CmRmIDwtIGp1bGlldGFfcnMgJT4lCiAgZ3JheXNjYWxlKCkgJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBtdXRhdGUoeSA9IC0oeSAtIG1heCh5KSkpCmBgYAoKVGhpcyB0aW1lLCB0aG91Z2gsIHdlIGFsc28gY29udmVydCBpbWFnZSB0byBhIGRhdGEgZnJhbWUgYnV0IHJldHJpZXZlIHRoZSBjb2xvcnM6CmBgYHtyfQpjb2xvcl9kZiA8LSBqdWxpZXRhX3JzICU+JQogIGFzLmRhdGEuZnJhbWUod2lkZT0iYyIpICU+JSAKICAjIFJldmVyc2UgdGhlIHkgYXhpcwogIG11dGF0ZSh5ID0gLSh5IC0gbWF4KHkpKSwKICAgICAgICAgaGV4X2NvbG9yID0gcmdiKGMuMSwKICAgICAgICAgICAgICAgICAgICAgICAgIGMuMiwKICAgICAgICAgICAgICAgICAgICAgICAgIGMuMykpCmBgYAoKQ3JlYXRlIGRhdGEgZnJhbWUgZm9yIHRoZSBtb3NhaWM6CmBgYHtyfQojIFRoaXMgd2lsbCB1c2UgYSBzbWFsbGVyIHN1YnNldCBvZiBwb2ludHMgdG8gY3JlYXRlIHRoZSBtb3NhaWMsIHdoaWNoIHdpbGwgdGhlbiBiZSByZXNjYWxlZApzIDwtIDEwCgp4bGltIDwtIGMobWluKGRmJHgpL3MgLSA0LCBtYXgoZGYkeCkvcyArIDQpCnlsaW0gPC0gYyhtaW4oZGYkeSkvcyAtIDQsIG1heChkZiR5KS9zICsgNCkKCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSBzcG90cyBmb3IgdGlsZXMKbV8xIDwtIGV4cGFuZC5ncmlkKHggPSBzZXEoeGxpbVsxXSwgeGxpbVsyXSwgMSksCiAgICAgICAgICAgICAgICAgICB5ID0gc2VxKHlsaW1bMV0sIHlsaW1bMl0sIDEpKSAlPiUKICBtdXRhdGUodGlsZXMgPSBzYW1wbGUoYygiZGwiLCAiZHIiKSwgbigpLCByZXBsYWNlID0gVFJVRSksCiAgICAgICAgIHNjYWxlX3AgPSAxKQpgYGAKCkNyZWF0ZSBtb3NhaWMgdXNpbmcgdGhlIGRlc2lnbmVkIGNvbnRhaW5lcjoKYGBge3J9Cm1fMSA8LSBzdF90cnVjaGV0X21zKGRmID0gbV8xKSAlPiUgCiAgc3RfdHJ1Y2hldF9kaXNzb2x2ZSgpCmBgYAoKRGlzc29sdmUgYW5kIGJ1ZmZlcjoKYGBge3J9Cm1fMiA8LSBtXzEgJT4lIAogIHN0X2J1ZmZlcihkaXN0ID0gLTAuMSkgJT4lCiAgbXV0YXRlKGNvbG9yID0gY29sb3IgKyAyKQoKbV8yIDwtIG1fMlshc3RfaXNfZW1wdHkobV8yKSwgLCBkcm9wID0gRkFMU0VdCgptXzMgPC0gbV8yICU+JSAKICBzdF90cnVjaGV0X2Rpc3NvbHZlKCkgJT4lIAogIHN0X2J1ZmZlcihkaXN0ID0gLTAuMSkgJT4lCiAgbXV0YXRlKGNvbG9yID0gY29sb3IgKyAyKQoKbV8zIDwtIG1fM1shc3RfaXNfZW1wdHkobV8zKSwgLCBkcm9wID0gRkFMU0VdCmBgYAoKYGBge3J9Cm1fMV9saW5lcyA8LSBtXzEgJT4lIAogIHN0X2Nhc3QodG8gPSAiTVVMVElMSU5FU1RSSU5HIikKbV8yX2xpbmVzIDwtIG1fMiAlPiUgCiAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKQptXzNfbGluZXMgPC0gbV8zICU+JSAKICBzdF9jYXN0KHRvID0gIk1VTFRJTElORVNUUklORyIpCmBgYAoKTm93IHRoaXMgbmVlZHMgdG8gYmUgc2NhbGVkIHRvIHRoZSBzaXplIG9mIHRoZSBpbWFnZS4gRmlyc3QgZ2V0IHRoZSB1bmlvbiBvZiB0aGUgZ2VvbWV0cmllczoKYGBge3J9Cm1fMV91bmlvbiA8LSBzdF91bmlvbihtXzEpCm1fMl91bmlvbiA8LSBzdF91bmlvbihtXzIpCmBgYAoKVGhlbiBzY2FsZSBhbmQgcmVjZW50ZXI6CmBgYHtyfQptXzFfdW5pb24gPC0gKG1fMV9saW5lcyAqIHMpICU+JQogIHN0X3NmKCkKbV8yX3VuaW9uIDwtIChtXzJfbGluZXMgKiBzKSAlPiUgCiAgc3Rfc2YoKQptXzNfdW5pb24gPC0gKG1fM19saW5lcyAqIHMpICU+JSAKICBzdF9zZigpCmBgYAoKUHV0IGl0IGFsbCB0b2dldGhlcjoKYGBge3J9Cm1vc2FpYyA8LSByYmluZChtXzFfdW5pb24sCiAgICAgICAgICAgICAgICBtXzJfdW5pb24sCiAgICAgICAgICAgICAgICBtXzNfdW5pb24pCmBgYAoKQ3JlYXRlIGEgYmxhZGU6CmBgYHtyfQpiYm94IDwtIHN0X2Jib3gobW9zYWljKSAlPiUgCiAgcm91bmQoKQoKYmxhZGUgPC0gZGF0YS5mcmFtZSh4X3N0YXJ0ID0gYyhiYm94JHhtaW46YmJveCR4bWF4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoYmJveCR5bWluLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKGJib3gkeW1pbjpiYm94JHltYXgpKSksCiAgICAgICAgICAgICAgICAgICAgeF9lbmQgPSBjKGJib3gkeG1pbjpiYm94JHhtYXgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoYmJveCR4bWF4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChiYm94JHltaW46YmJveCR5bWF4KSkpLAogICAgICAgICAgICAgICAgICAgIHlfc3RhcnQgPSBjKHJlcChiYm94JHltaW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoYmJveCR4bWluOmJib3gkeG1heCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJib3gkeW1pbjpiYm94JHltYXgpLAogICAgICAgICAgICAgICAgICAgIHlfZW5kID0gYyhyZXAoYmJveCR5bWF4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKGJib3gkeG1pbjpiYm94JHhtYXgpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmJveCR5bWluOmJib3gkeW1heCkpCgojIFNoaWZ0IHRoZSBibGFkZSBhIHNtYWxsIGFtb3VudCB0byBhdm9pZCBwZXJmZWN0IG92ZXJsYXAgd2l0aCB1bmRlcmx5aW5nIGdyaWQKYmxhZGUgPC0gYmxhZGUgJT4lCiAgbXV0YXRlKGFjcm9zcyhldmVyeXRoaW5nKCksIAogICAgICAgICAgICAgICAgfiAueCArIDAuMjgpKQoKYmxhZGUgPC0gcG1hcChibGFkZSwgZnVuY3Rpb24oeF9zdGFydCwgeF9lbmQsIHlfc3RhcnQsIHlfZW5kKXsKICBzdF9saW5lc3RyaW5nKAogICAgbWF0cml4KAogICAgICBjKAogICAgICAgIHhfc3RhcnQsCiAgICAgICAgeV9zdGFydCwKICAgICAgICB4X2VuZCwKICAgICAgICB5X2VuZCksCiAgICAgIG5jb2wgPSAyLGJ5cm93ID0gVFJVRSkKICApCn0pICU+JQogIHN0X2FzX3NmYygpCmBgYAoKVXNlIHRoZSBibGFkZSB0byBzcGxpdCB0aGUgbGluZXM6CmBgYHtyfQptb3NhaWNfbGluZXMgPC0gbW9zYWljICU+JQogIHN0X3NwbGl0KGJsYWRlKQpgYGAKCkV4dHJhY3QgdGhlIGdlb21ldHJpZXM6CmBgYHtyfQptb3NhaWNfbGluZXMgPC0gbW9zYWljX2xpbmVzICU+JQogIHN0X2NvbGxlY3Rpb25fZXh0cmFjdCh0eXBlID0gIkxJTkVTVFJJTkciKSAlPiUKICBzdF9jYXN0KHRvID0gIkxJTkVTVFJJTkciKSAlPiUKICBtdXRhdGUoaWQgPSAxOm4oKSkKYGBgCgpDb252ZXJ0IHRoZSBkYXRhIGZyYW1lcyB3aXRoIHRoZSBpbWFnZSB0byBzaW1wbGUgZmVhdHVyZXMuIFRoaXMgd2F5IHdlIGNhbiB1c2UgZnVuY3Rpb25zIGZyb20gdGhlIHtzZn0gcGFja2FnZSB0byBmaW5kIHRoZSBuZWFyZXN0IGZlYXR1cmUgdG8gYm9ycm93IHRoZSBvcmlnaW5hbCBjb2xvcnMgaW4gdGhlIGltYWdlOgpgYGB7cn0KZGZfc2YgPC0gZGYgJT4lCiAgc3RfYXNfc2YoY29vcmRzID0gYygieCIsICJ5IikpCgpjb2xvcl9kZl9zZiA8LSBjb2xvcl9kZiAlPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJ4IiwgInkiKSkKYGBgCgpGaW5kIHRoZSBuZWFyZXN0IGZlYXR1cmUgYW5kIGJvcnJvdyB0b25lcyBvZiBncmF5IGFuZCBoZXhhZGVjaW1hbCBjb2xvcnM6CmBgYHtyfQp2YWx1ZSA8LSBkZl9zZlttb3NhaWNfbGluZXMgJT4lIAogICAgICAgICAgICAgICAgIHN0X25lYXJlc3RfZmVhdHVyZShkZl9zZiksXSAlPiUKICBwdWxsKHZhbHVlKQoKaGV4X2NvbG9yIDwtIGNvbG9yX2RmX3NmW21vc2FpY19saW5lcyAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0X25lYXJlc3RfZmVhdHVyZShjb2xvcl9kZl9zZiksXSAlPiUKICBwdWxsKGhleF9jb2xvcikKYGBgCgpXZSBjYW4gbm93IGFkZCB0aGUgZ3JleXNjYWxlIHZhbHVlcyBhbmQgaGV4YWRlY2ltYWwgY29sb3JzIHRvIHRoZSBkYXRhIGZyYW1lIHdpdGggdGhlIG1vc2FpYzoKYGBge3J9Cm1vc2FpY19saW5lcyR2YWx1ZSA8LSB2YWx1ZQptb3NhaWNfbGluZXMkaGV4X2NvbG9yIDwtIGhleF9jb2xvcgpgYGAKClBsb3QgbW9zYWljOgpgYGB7ciBldmFsPUZBTFNFfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gbW9zYWljX2xpbmVzICU+JQogICAgICAgICAgICBzdF9jcm9wKGRmX3NmKSwKICAgICAgICAgIGFlcyhjb2xvciA9IGhleF9jb2xvciwKICAgICAgICAgICAgICBzaXplID0gZXhwKC0yICogdmFsdWUpKSkgKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDAuMDEsIDAuODApKSArIAogIGNvb3JkX3NmKGV4cGFuZCA9IEZBTFNFKSArIAogIHRoZW1lX3ZvaWQoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigwLjEsIDAuMSwgMC4xLCAwLjEsICJpbiIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBtZXguYnJld2VyKCJSZXZvbHVjaW9uIilbNV0pLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9IE5BLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gbWV4LmJyZXdlcigiUmV2b2x1Y2lvbiIpWzVdKSkKCmdnc2F2ZSgidHJ1Y2hldC1vdmFsbGUucG5nIiwKICAgICAgIGhlaWdodCA9IDYuNSwKICAgICAgIHdpZHRoID0gNSwKICAgICAgIHVuaXRzID0gImluIikKYGBgCgpQbG90IG1vc2FpYzoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfbGluZXMgJT4lCiAgICAgICAgICAgIHN0X2Nyb3AoZGZfc2YpLAogICAgICAgICAgYWVzKGNvbG9yID0gaGV4X2NvbG9yLAogICAgICAgICAgICAgIHNpemUgPSBleHAoLTIgKiB2YWx1ZSkpKSArCiAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMC4wMSwgMC44MCkpICsgCiAgY29vcmRfc2YoZXhwYW5kID0gRkFMU0UpICsgCiAgdGhlbWVfdm9pZCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDAuMSwgMC4xLCAwLjEsIDAuMSwgImluIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9IE5BLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICJ3aGl0ZSIpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9IE5BLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gIndoaXRlIikpCgpnZ3NhdmUoInRydWNoZXQtb3ZhbGxlLXcucG5nIiwKICAgICAgIGhlaWdodCA9IDYuNSwKICAgICAgIHdpZHRoID0gNSwKICAgICAgIHVuaXRzID0gImluIikKYGBgCgojIyBBIG1vcmUgY29tcGxleCBleGFtcGxlIHdpdGggbXV0bGlwbGUgY29udGFpbmVycwoKUmVhZCB0aGUgaW1hZ2UgdXNpbmcgYGltYWdlcjo6bG9hZC5pbWFnZSgpYDoKYGBge3J9CmNsb3VkcyA8LSBsb2FkLmltYWdlKCJjbG91ZHMuanBnIikKYGBgCgpJbWFnZSBpbmZvOgpgYGB7cn0KY2xvdWRzCmBgYAoKYGBge3J9CmNsb3Vkc19ycyA8LSBpbXJlc2l6ZShjbG91ZHMsIHNjYWxlID0gMS8xMCwgaW50ZXJwb2xhdGlvbiA9IDYpCmBgYAoKYGBge3J9CmNsb3Vkcy5nIDwtIGdyYXlzY2FsZShjbG91ZHNfcnMpCmBgYAoKQ29udmVydCB0byBkYXRhIGZyYW1lOgpgYGB7cn0KZGYgPC0gY2xvdWRzX3JzICU+JQogIGdyYXlzY2FsZSgpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKHkgPSAtKHkgLSBtYXgoeSkpKQpgYGAKClRoaXMgdGltZSwgdGhvdWdoLCB3ZSBhbHNvIGNvbnZlcnQgaW1hZ2UgdG8gYSBkYXRhIGZyYW1lIGJ1dCByZXRyaWV2ZSB0aGUgY29sb3JzOgpgYGB7cn0KY29sb3JfZGYgPC0gY2xvdWRzX3JzICU+JQogIGFzLmRhdGEuZnJhbWUod2lkZT0iYyIpICU+JSAKICAjIFJldmVyc2UgdGhlIHkgYXhpcwogIG11dGF0ZSh5ID0gLSh5IC0gbWF4KHkpKSwKICAgICAgICAgaGV4X2NvbG9yID0gcmdiKGMuMSwKICAgICAgICAgICAgICAgICAgICAgICAgIGMuMiwKICAgICAgICAgICAgICAgICAgICAgICAgIGMuMykpCmBgYAoKQmluZCBncmF5c2NhbGUgYW5kIGhleGFkZWNpbWFsIGNvbG9ycyBpbiB0aGUgc2FtZSBkYXRhIGZyYW1lOgpgYGB7cn0KZGYgPC0gY2JpbmQoZGYsCiAgICAgICAgICAgIGNvbG9yX2RmICU+JQogICAgICAgICAgICAgIHNlbGVjdChoZXhfY29sb3IpKQpgYGAKCkNvbnZlcnQgdGhlIGRhdGEgZnJhbWUgdG8gc2ltcGxlIGZlYXR1cmVzOgpgYGB7cn0KZGZfc2YgPC0gZGYgJT4lCiAgc3RfYXNfc2YoY29vcmRzID0gYygieCIsICJ5IikpICU+JQogIGNiaW5kKGRmICU+JSBzZWxlY3QoeCwgeSkpCmBgYAoKSWRlbnRpZnkgcGF0aHMgZm9yIGJsYWRlcyB0byBzcGxpdCBpbWFnZToKYGBge3J9CmltIDwtIGNsb3Vkcy5nCgojI1RoZSBmb2xsb3dpbmcgZnVuY3Rpb24gbWFrZXMgYSBkYXRhLmZyYW1lIG9mIGxpbmtzIGJldHdlZW4gcGl4ZWwgKHgseSkgYW5kIHBpeGVsICh4K2R4LHkrZHkpCiMjSSdtIHN1cmUgdGhlcmUncyBhIGJldHRlciB3YXkgb2YgZG9pbmcgdGhpbmdzCm1ha2UuZGYgPC0gZnVuY3Rpb24oZHgsZHkpewogIChhYnMoaW0taW1zaGlmdChpbSxkeCxkeSkpKSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIG11dGF0ZSh4LnRvID0geC1keCwgeS50byA9IHktZHksIGlkLmZyb20gPSBwYXN0ZSh4LHksc2VwPSIsIiksIGlkLnRvID0gcGFzdGUoeC50byx5LnRvLHNlcD0iLCIpKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoaWQuZnJvbSwgaWQudG8sIHZhbHVlKSAlPiUKICAgIGRwbHlyOjpyZW5hbWUod2VpZ2h0PXZhbHVlKX0KCiMgUGF0aCAxCiMjIEdldCBhbGwgbmVpZ2hib3VycywgY29udmVydCBkYXRhLmZyYW1lIHRvIGdyYXBoCkcgPC0gY3Jvc3MyKC0xOjEsLTE6MSxmdW5jdGlvbihhLGIpIGFicyhhKSArYWJzKGIpID09IDApICU+JQogIG1hcF9kZihsaWZ0KGZ1bmN0aW9uKGR4LGR5KSBtdXRhdGUobWFrZS5kZihkeCxkeSksZHg9ZHgsZHk9ZHkpKSkgJT4lCiAgbXV0YXRlKHdlaWdodCA9IDEvKHdlaWdodCArIDAuMDEpXjcpI2V4cCgtMC4wNSAqIHdlaWdodCkpCgpHIDwtIEcgJT4lCiAgZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKCkKCiNFeHRyYWN0IHNob3J0ZXN0IHBhdGhzCnBhdGhfMSA8LSBzaG9ydGVzdF9wYXRocyhHLCIxLDE0MCIsIjI1OCwxNTAiKSAlJCUgVihHKVt2cGF0aFtbMV1dXSU+JQogIG5hbWVzICU+JSBzdHJpbmdyOjpzdHJfc3BsaXQoIiwiKSAlPiUKICBtYXBfZGYofiBkYXRhLmZyYW1lKHg9YXMuaW50ZWdlciguW1sxXV0pLHk9YXMuaW50ZWdlciguW1syXV0pKSkKCiMgUGF0aCAyCiMjIEdldCBhbGwgbmVpZ2hib3VycywgY29udmVydCBkYXRhLmZyYW1lIHRvIGdyYXBoCkcgPC0gY3Jvc3MyKC0xOjEsLTE6MSxmdW5jdGlvbihhLGIpIGFicyhhKSArYWJzKGIpID09IDApICU+JQogIG1hcF9kZihsaWZ0KGZ1bmN0aW9uKGR4LGR5KSBtdXRhdGUobWFrZS5kZihkeCxkeSksZHg9ZHgsZHk9ZHkpKSkgJT4lCiAgbXV0YXRlKHdlaWdodCA9IDEvKHdlaWdodCArIDAuMDEpXjEpI2V4cCgtMC4wNSAqIHdlaWdodCkpCgpHIDwtIEcgJT4lCiAgZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKCkKCiNFeHRyYWN0IHNob3J0ZXN0IHBhdGhzCnBhdGhfMiA8LSBzaG9ydGVzdF9wYXRocyhHLCIxLDE1MCIsIjI1OCwxNTAiKSAlJCUgVihHKVt2cGF0aFtbMV1dXSU+JQogIG5hbWVzICU+JSBzdHJpbmdyOjpzdHJfc3BsaXQoIiwiKSAlPiUKICBtYXBfZGYofiBkYXRhLmZyYW1lKHg9YXMuaW50ZWdlciguW1sxXV0pLHk9YXMuaW50ZWdlciguW1syXV0pKSkKCiMgUGF0aCAzCiMjIEdldCBhbGwgbmVpZ2hib3VycywgY29udmVydCBkYXRhLmZyYW1lIHRvIGdyYXBoCkcgPC0gY3Jvc3MyKC0xOjEsLTE6MSxmdW5jdGlvbihhLGIpIGFicyhhKSArYWJzKGIpID09IDApICU+JQogIG1hcF9kZihsaWZ0KGZ1bmN0aW9uKGR4LGR5KSBtdXRhdGUobWFrZS5kZihkeCxkeSksZHg9ZHgsZHk9ZHkpKSkgJT4lCiAgbXV0YXRlKHdlaWdodCA9IDEvKHdlaWdodCArIDAuMDEpXjIpI2V4cCgtMC4wNSAqIHdlaWdodCkpCgpHIDwtIEcgJT4lCiAgZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKCkKCiNFeHRyYWN0IHNob3J0ZXN0IHBhdGhzCnBhdGhfMyA8LSBzaG9ydGVzdF9wYXRocyhHLCIxLDIwNSIsIjI1OCwyMDAiKSAlJCUgVihHKVt2cGF0aFtbMV1dXSU+JQogIG5hbWVzICU+JSBzdHJpbmdyOjpzdHJfc3BsaXQoIiwiKSAlPiUKICBtYXBfZGYofiBkYXRhLmZyYW1lKHg9YXMuaW50ZWdlciguW1sxXV0pLHk9YXMuaW50ZWdlciguW1syXV0pKSkKYGBgCgpDaGVjayB0aGUgcGF0aHM6CmBgYHtyfQpwbG90KGltKQpsaW5lcyhwYXRoXzEkeCxwYXRoXzEkeSxjb2w9ImdyZWVuIixsdHk9Mixsd2Q9MikKbGluZXMocGF0aF8yJHgscGF0aF8yJHksY29sPSJyZWQiLGx0eT0yLGx3ZD0yKQpsaW5lcyhwYXRoXzMkeCxwYXRoXzMkeSxjb2w9ImJsdWUiLGx0eT0yLGx3ZD0yKQpgYGAKClJldmVyc2UgdGhlIHkgYXhpcyBvZiB0aGUgcGF0aHMgYW5kIHNoaWZ0IHRoZSB5IGNvb3JkaW5hdGVzOgpgYGB7cn0KcGF0aF8xIDwtIHBhdGhfMSAlPiUKICBtdXRhdGUoeSA9IC0oeSAtIG1heCh5KSkgKyAxMDcpCgpwYXRoXzIgPC0gcGF0aF8yICU+JQogIG11dGF0ZSh5ID0gLSh5IC0gbWF4KHkpKSArIDY4KQoKcGF0aF8zIDwtIHBhdGhfMyAlPiUKICBtdXRhdGUoeSA9IC0oeSAtIG1heCh5KSkgKyAzMCkKYGBgCgpDb252ZXJ0IHBhdGhzIHRvIHNpbXBsZSBmZWF0dXJlczoKYGBge3J9CnBhdGhfMSA8LSBkYXRhLmZyYW1lKHBhdGggPSAiMSIsIAogICAgICAgICAgICAgICAgICAgICBzdF9saW5lc3RyaW5nKHggPSBhcy5tYXRyaXgocGF0aF8xKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgc3RfZ2VvbWV0cnkoKSkgJT4lCiAgc3Rfc2YoKQoKcGF0aF8yIDwtIGRhdGEuZnJhbWUocGF0aCA9ICIyIiwgCiAgICAgICAgICAgICAgICAgICAgIHN0X2xpbmVzdHJpbmcoeCA9IGFzLm1hdHJpeChwYXRoXzIpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICBzdF9nZW9tZXRyeSgpKSAlPiUKICBzdF9zZigpCgpwYXRoXzMgPC0gZGF0YS5mcmFtZShwYXRoID0gIjMiLCAKICAgICAgICAgICAgICAgICAgICAgc3RfbGluZXN0cmluZyh4ID0gYXMubWF0cml4KHBhdGhfMykpICU+JQogICAgICAgICAgICAgICAgICAgICAgIHN0X2dlb21ldHJ5KCkpICU+JQogIHN0X3NmKCkKCgojIFB1dCB0b2dldGhlcgpwYXRocyA8LSByYmluZChwYXRoXzEgLAogICAgICAgICAgICAgICBwYXRoXzIsCiAgICAgICAgICAgICAgIHBhdGhfMykgJT4lCiAgIyBTaW1wbGlmeSBwYXRocwogIHN0X3NpbXBsaWZ5KGRUb2xlcmFuY2UgPSAyKQpgYGAKCkNyZWF0ZSBjb250YWluZXI6CmBgYHtyfQpjb250YWluZXIgPC0gbWF0cml4KGMobWluKGRmJHgpICsgMSwgbWluKGRmJHkpICsgMiwKICAgICAgICAgICAgICAgICAgICAgIG1pbihkZiR4KSArIDEsIG1heChkZiR5KSAtIDIsCiAgICAgICAgICAgICAgICAgICAgICBtYXgoZGYkeCkgLSAyLCBtYXgoZGYkeSkgLSAyLAogICAgICAgICAgICAgICAgICAgICAgbWF4KGRmJHgpIC0gMiwgbWluKGRmJHkpICsgMiwKICAgICAgICAgICAgICAgICAgICAgIG1pbihkZiR4KSArIDEsIG1pbihkZiR5KSArIDIpLAogICAgICAgICAgICAgICAgICAgIG5jb2wgPSAyLAogICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSkKCiMgQ29udmVydCBjb29yZGluYXRlcyB0byBwb2x5Z29ucyBhbmQgdGhlbiB0byBzaW1wbGUgZmVhdHVyZXMKY29udGFpbmVyIDwtIGRhdGEuZnJhbWUoZ2VvbWV0cnkgPSBzZjo6c3RfcG9seWdvbihsaXN0KGNvbnRhaW5lcikpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgIHNmOjpzdF9zZmMoKSkgJT4lCiAgc2Y6OnN0X2FzX3NmKCkKYGBgCgpQbG90IGNvbnRhaW5lciBhbmQgcGF0aHMKYGBge3J9CmdncGxvdCgpICsgCiAgIyBnZW9tX3NmKGRhdGEgPSBkZl9zZiwKICAjICAgICAgICAgYWVzKGNvbG9yID0gdmFsdWUpKSArCiAgZ2VvbV9zZihkYXRhICA9IHBhdGhzLAogICAgICAgICAgYWVzKGNvbG9yID0gcGF0aCkpICsgCiAgZ2VvbV9zZihkYXRhID0gY29udGFpbmVyLAogICAgICAgICAgY29sb3IgPSAicHVycGxlIiwKICAgICAgICAgIGZpbGwgPSBOQSkKYGBgCgpVc2UgdGhlIHBhdGhzIHRvIHNwbGl0IHRoZSBjb250YWluZXI6CmBgYHtyfQpjb250YWluZXIgPC0gY29udGFpbmVyICU+JQogIHN0X3NwbGl0KHBhdGhzKSAlPiUKICBzdF9jb2xsZWN0aW9uX2V4dHJhY3QoKSAlPiUKICBtdXRhdGUoaWQgPSAxOm4oKSkKYGBgCgpQbG90OgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGNvbnRhaW5lciwKICAgICAgICAgIGFlcyhmaWxsID0gZmFjdG9yKGlkKSkpCmBgYAoKU3BhdGlhbCBqb2luIHRoZSBkYXRhIGZyYW1lIHdpdGggdGhlIGltYWdlLiBDcmVhdGUgYnVmZmVycyBvZiB0aGUgcGFydHMgb2YgdGhlIGNvaW50YWluZXIgdG8gaGF2ZSBzb21lIG92ZXJsYXAgYmV0d2VlbiB0aGUgbW9zYWljcyB0byBhdm9pZCBibGFua3MgaW4gdGhlIGZpbmFsIG1vc2FpYzoKYGBge3J9CmRmX3NmIDwtIGRmX3NmICU+JQogIHN0X2pvaW4oY29udGFpbmVyKQoKZGZfMV9zZiA8LSBkZl9zZiAlPiUKICBzZWxlY3QoLWlkKSAlPiUKICBzdF9qb2luKGNvbnRhaW5lciAlPiUKICAgICAgICAgICAgZmlsdGVyKGlkID09ICIxIikgJT4lCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gMTApKQoKZGZfMl9zZiA8LSBkZl9zZiAlPiUKICBzZWxlY3QoLWlkKSAlPiUKICBzdF9qb2luKGNvbnRhaW5lciAlPiUKICAgICAgICAgICAgZmlsdGVyKGlkID09ICIyIikgJT4lCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gMTApKQoKZGZfM19zZiA8LSBkZl9zZiAlPiUKICBzZWxlY3QoLWlkKSAlPiUKICBzdF9qb2luKGNvbnRhaW5lciAlPiUKICAgICAgICAgICAgZmlsdGVyKGlkID09ICIzIikgJT4lCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gMTApKQoKZGZfNF9zZiA8LSBkZl9zZiAlPiUKICBzZWxlY3QoLWlkKSAlPiUKICBzdF9qb2luKGNvbnRhaW5lciAlPiUKICAgICAgICAgICAgZmlsdGVyKGlkID09ICI0IikgJT4lCiAgICAgICAgICAgIHN0X2J1ZmZlcihkaXN0ID0gMTApKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IGRmXzFfc2YgJT4lIAogICAgICAgICAgICBmaWx0ZXIoeCAlJSA1ID09IDAsIAogICAgICAgICAgICAgICAgICAgeSAlJSA1ID09IDApLAogICAgICAgICAgYWVzKGNvbG9yID0gaWQsCiAgICAgICAgICAgICAgc2hhcGUgPSBmYWN0b3IoaWQpKSkgICsgCiAgZ2VvbV9zZihkYXRhID0gZGZfMl9zZiAlPiUgCiAgICAgICAgICAgIGZpbHRlcih4ICUlIDUgPT0gMCwgCiAgICAgICAgICAgICAgICAgICB5ICUlIDUgPT0gMCksCiAgICAgICAgICBhZXMoY29sb3IgPSBpZCwKICAgICAgICAgICAgICBzaGFwZSA9IGZhY3RvcihpZCkpKSArIAogIGdlb21fc2YoZGF0YSA9IGRmXzNfc2YgJT4lIAogICAgICAgICAgICBmaWx0ZXIoeCAlJSA1ID09IDAsIAogICAgICAgICAgICAgICAgICAgeSAlJSA1ID09IDApLAogICAgICAgICAgYWVzKGNvbG9yID0gaWQsCiAgICAgICAgICAgICAgc2hhcGUgPSBmYWN0b3IoaWQpKSkgKyAKICBnZW9tX3NmKGRhdGEgPSBkZl80X3NmICU+JSAKICAgICAgICAgICAgZmlsdGVyKHggJSUgNSA9PSAwLCAKICAgICAgICAgICAgICAgICAgIHkgJSUgNSA9PSAwKSwKICAgICAgICAgIGFlcyhjb2xvciA9IGlkLAogICAgICAgICAgICAgIHNoYXBlID0gZmFjdG9yKGlkKSkpCmBgYAoKQ3JlYXRlIGRhdGEgZnJhbWVzIGZvciB0aGUgbW9zYWljOgpgYGB7cn0KIyBUaGlzIHdpbGwgdXNlIGEgc21hbGxlciBzdWJzZXQgb2YgcG9pbnRzIHRvIGNyZWF0ZSB0aGUgbW9zYWljLCB3aGljaCB3aWxsIHRoZW4gYmUgcmVzY2FsZWQKcyA8LSAxNQoKIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggdGhlIHNwb3RzIGZvciB0aWxlcwptXzQgPC0gZGZfNF9zZiAlPiUKICBmaWx0ZXIoaWQgPT0gNCwgCiAgICAgICAgIHggJSUgcyA9PSAwLCAKICAgICAgICAgeSAlJSBzID09IDApICU+JQogIG11dGF0ZSh4ID0geC9zLAogICAgICAgICB5ID0geS9zLAogICAgICAgICB0aWxlcyA9IHNhbXBsZShjKCItIiwgInwiLCAidG4iKSwgbigpLCByZXBsYWNlID0gVFJVRSksCiAgICAgICAgIHNjYWxlX3AgPSAxKQoKbV8zIDwtIGRmXzNfc2YgJT4lCiAgZmlsdGVyKGlkID09IDMsIAogICAgICAgICB4ICUlIHMgPT0gMCwgCiAgICAgICAgIHkgJSUgcyA9PSAwKSAlPiUKICBtdXRhdGUoeCA9IHgvcywKICAgICAgICAgeSA9IHkvcywKICAgICAgICAgdGlsZXMgPSBzYW1wbGUoYygiZnNlIiwgImZzdyIsICIrIiksIG4oKSwgcmVwbGFjZSA9IFRSVUUpLAogICAgICAgICBzY2FsZV9wID0gMSkKCm1fMiA8LSBkZl8yX3NmICU+JQogIGZpbHRlcihpZCA9PSAyLCAKICAgICAgICAgeCAlJSBzID09IDAsIAogICAgICAgICB5ICUlIHMgPT0gMCkgJT4lCiAgbXV0YXRlKHggPSB4L3MsCiAgICAgICAgIHkgPSB5L3MsCiAgICAgICAgIHRpbGVzID0gc2FtcGxlKGMoImRsIiwgImRyIiksIG4oKSwgcmVwbGFjZSA9IFRSVUUpLAogICAgICAgICBzY2FsZV9wID0gMSkKCm1fMSA8LSBkZl8xX3NmICU+JQogIGZpbHRlcihpZCA9PSAxLCAKICAgICAgICAgeCAlJSBzID09IDAsIAogICAgICAgICB5ICUlIHMgPT0gMCkgJT4lCiAgbXV0YXRlKHggPSB4L3MsCiAgICAgICAgIHkgPSB5L3MsCiAgICAgICAgIHRpbGVzID0gc2FtcGxlKGMoImZuZSIsICJmbnciLCAiKyIpLCBuKCksIHJlcGxhY2UgPSBUUlVFKSwKICAgICAgICAgc2NhbGVfcCA9IDEpCmBgYAoKQ3JlYXRlIG1vc2FpYyB1c2luZyB0aGUgZGVzaWduZWQgY29udGFpbmVyOgpgYGB7cn0KIyBQYXJ0IDQKbV80IDwtIHN0X3RydWNoZXRfbXMoZGYgPSBtXzQgJT4lIAogICAgICAgICAgICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSkgJT4lCiAgc3RfdHJ1Y2hldF9kaXNzb2x2ZSgpCgojIFBhcnQgMwptXzMgPC0gc3RfdHJ1Y2hldF9tcyhkZiA9IG1fMyAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpKSAlPiUKICBzdF90cnVjaGV0X2Rpc3NvbHZlKCkKCiMgUGFydCAyCm1fMiA8LSBzdF90cnVjaGV0X21zKGRmID0gbV8yICU+JSAKICAgICAgICAgICAgICAgICAgICAgICBzdF9kcm9wX2dlb21ldHJ5KCkpICU+JQogIHN0X3RydWNoZXRfZGlzc29sdmUoKQoKIyBQYXJ0IDEKbV8xIDwtIHN0X3RydWNoZXRfbXMoZGYgPSBtXzEgJT4lIAogICAgICAgICAgICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSkgJT4lCiAgc3RfdHJ1Y2hldF9kaXNzb2x2ZSgpCgpgYGAKClBsb3Q6CmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IG1fNCkgKyAKICBnZW9tX3NmKGRhdGEgPSBtXzMpICsgCiAgZ2VvbV9zZihkYXRhID0gbV8yKSArIAogIGdlb21fc2YoZGF0YSA9IG1fMSkgCmBgYAoKRGlzc29sdmUgYW5kIGJ1ZmZlcjoKYGBge3J9CiMgQ29udGFpbmVyIDQKbV80YjEgPC0gbV80ICU+JSAKICBzdF9idWZmZXIoZGlzdCA9IC0wLjEpICU+JQogIG11dGF0ZShjb2xvciA9IGNvbG9yICsgMikKCm1fNGIxIDwtIG1fNGIxWyFzdF9pc19lbXB0eShtXzRiMSksICwgZHJvcCA9IEZBTFNFXQoKbV80YjIgPC0gbV80YjEgJT4lIAogIHN0X3RydWNoZXRfZGlzc29sdmUoKSAlPiUgCiAgc3RfYnVmZmVyKGRpc3QgPSAtMC4xKSAlPiUKICBtdXRhdGUoY29sb3IgPSBjb2xvciArIDIpCgptXzRiMiA8LSBtXzRiMlshc3RfaXNfZW1wdHkobV80YjIpLCAsIGRyb3AgPSBGQUxTRV0KCiMgQ29udGFpbmVyIDMKbV8zYjEgPC0gbV8zICU+JSAKICBzdF9idWZmZXIoZGlzdCA9IC0wLjEpICU+JQogIG11dGF0ZShjb2xvciA9IGNvbG9yICsgMikKCm1fM2IxIDwtIG1fM2IxWyFzdF9pc19lbXB0eShtXzNiMSksICwgZHJvcCA9IEZBTFNFXQoKbV8zYjIgPC0gbV8zYjEgJT4lIAogIHN0X3RydWNoZXRfZGlzc29sdmUoKSAlPiUgCiAgc3RfYnVmZmVyKGRpc3QgPSAtMC4xKSAlPiUKICBtdXRhdGUoY29sb3IgPSBjb2xvciArIDIpCgptXzNiMiA8LSBtXzNiMlshc3RfaXNfZW1wdHkobV8zYjIpLCAsIGRyb3AgPSBGQUxTRV0KCiMgQ29udGFpbmVyIDIKbV8yYjEgPC0gbV8yICU+JSAKICBzdF9idWZmZXIoZGlzdCA9IC0wLjEpICU+JQogIG11dGF0ZShjb2xvciA9IGNvbG9yICsgMikKCm1fMmIxIDwtIG1fMmIxWyFzdF9pc19lbXB0eShtXzJiMSksICwgZHJvcCA9IEZBTFNFXQoKbV8yYjIgPC0gbV8yYjEgJT4lIAogIHN0X3RydWNoZXRfZGlzc29sdmUoKSAlPiUgCiAgc3RfYnVmZmVyKGRpc3QgPSAtMC4xKSAlPiUKICBtdXRhdGUoY29sb3IgPSBjb2xvciArIDIpCgptXzJiMiA8LSBtXzJiMlshc3RfaXNfZW1wdHkobV8yYjIpLCAsIGRyb3AgPSBGQUxTRV0KCiMgQ29udGFpbmVyIDEKbV8xYjEgPC0gbV8xICU+JSAKICBzdF9idWZmZXIoZGlzdCA9IC0wLjEpICU+JQogIG11dGF0ZShjb2xvciA9IGNvbG9yICsgMikKCm1fMWIxIDwtIG1fMWIxWyFzdF9pc19lbXB0eShtXzFiMSksICwgZHJvcCA9IEZBTFNFXQoKbV8xYjIgPC0gbV8xYjEgJT4lIAogIHN0X3RydWNoZXRfZGlzc29sdmUoKSAlPiUgCiAgc3RfYnVmZmVyKGRpc3QgPSAtMC4xKSAlPiUKICBtdXRhdGUoY29sb3IgPSBjb2xvciArIDIpCgptXzFiMiA8LSBtXzFiMlshc3RfaXNfZW1wdHkobV8xYjIpLCAsIGRyb3AgPSBGQUxTRV0KYGBgCgpgYGB7cn0KbV80X2xpbmVzIDwtIHJiaW5kKG1fNCAlPiUgCiAgICAgICAgICAgICAgICAgICAgIHN0X2Nhc3QodG8gPSAiTVVMVElMSU5FU1RSSU5HIiksCiAgICAgICAgICAgICAgICAgICBtXzRiMSAlPiUKICAgICAgICAgICAgICAgICAgICAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKSwKICAgICAgICAgICAgICAgICAgIG1fNGIyICU+JSAKICAgICAgICAgICAgICAgICAgICAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKSkKCm1fM19saW5lcyA8LSByYmluZChtXzMgJT4lIAogICAgICAgICAgICAgICAgICAgICBzdF9jYXN0KHRvID0gIk1VTFRJTElORVNUUklORyIpLAogICAgICAgICAgICAgICAgICAgbV8zYjEgJT4lCiAgICAgICAgICAgICAgICAgICAgIHN0X2Nhc3QodG8gPSAiTVVMVElMSU5FU1RSSU5HIiksCiAgICAgICAgICAgICAgICAgICBtXzNiMiAlPiUgCiAgICAgICAgICAgICAgICAgICAgIHN0X2Nhc3QodG8gPSAiTVVMVElMSU5FU1RSSU5HIikpCgptXzJfbGluZXMgPC0gcmJpbmQobV8yICU+JSAKICAgICAgICAgICAgICAgICAgICAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKSwKICAgICAgICAgICAgICAgICAgIG1fMmIxICU+JQogICAgICAgICAgICAgICAgICAgICBzdF9jYXN0KHRvID0gIk1VTFRJTElORVNUUklORyIpLAogICAgICAgICAgICAgICAgICAgbV8yYjIgJT4lIAogICAgICAgICAgICAgICAgICAgICBzdF9jYXN0KHRvID0gIk1VTFRJTElORVNUUklORyIpKQoKbV8xX2xpbmVzIDwtIHJiaW5kKG1fMSAlPiUgCiAgICAgICAgICAgICAgICAgICAgIHN0X2Nhc3QodG8gPSAiTVVMVElMSU5FU1RSSU5HIiksCiAgICAgICAgICAgICAgICAgICBtXzFiMSAlPiUKICAgICAgICAgICAgICAgICAgICAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKSwKICAgICAgICAgICAgICAgICAgIG1fMWIyICU+JSAKICAgICAgICAgICAgICAgICAgICAgc3RfY2FzdCh0byA9ICJNVUxUSUxJTkVTVFJJTkciKSkKYGBgCgpUaGVuIHNjYWxlIGFuZCByZWNlbnRlcjoKYGBge3J9Cm1fNF91bmlvbiA8LSAobV80X2xpbmVzICogcykgJT4lCiAgc3Rfc2YoKQoKbV8zX3VuaW9uIDwtIChtXzNfbGluZXMgKiBzKSAlPiUgCiAgc3Rfc2YoKQoKbV8yX3VuaW9uIDwtIChtXzJfbGluZXMgKiBzKSAlPiUgCiAgc3Rfc2YoKQoKbV8xX3VuaW9uIDwtIChtXzFfbGluZXMgKiBzKSAlPiUgCiAgc3Rfc2YoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IG1fNF91bmlvbiwKICAgICAgICAgIGNvbG9yID0gImJsdWUiKSArIAogIGdlb21fc2YoZGF0YSA9IG1fM191bmlvbiwKICAgICAgICAgIGNvbG9yID0gImdyZWVuIikgKyAKICBnZW9tX3NmKGRhdGEgPSBtXzJfdW5pb24sCiAgICAgICAgICBjb2xvciA9ICJyZWQiKSArIAogIGdlb21fc2YoZGF0YSA9IG1fMV91bmlvbiwKICAgICAgICAgIGNvbG9yID0gIm9yYW5nZSIpCmBgYAoKUHV0IGl0IGFsbCB0b2dldGhlcjoKYGBge3J9CiMgbW9zYWljIDwtIHJiaW5kKG1fMV91bmlvbiwKIyAgICAgICAgICAgICAgICAgbV8yX3VuaW9uLAojICAgICAgICAgICAgICAgICBtXzNfdW5pb24sCiMgICAgICAgICAgICAgICAgIG1fNF91bmlvbikKYGBgCgpDcmVhdGUgYSBncmlkIGZvciB0aGUgYmxhZGU6CmBgYHtyfQpiYm94IDwtIHN0X2Jib3gobW9zYWljKSAlPiUgCiAgcm91bmQoKQoKYmxhZGUgPC0gZGF0YS5mcmFtZSh4X3N0YXJ0ID0gYyhiYm94JHhtaW46YmJveCR4bWF4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoYmJveCR5bWluLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKGJib3gkeW1pbjpiYm94JHltYXgpKSksCiAgICAgICAgICAgICAgICAgICAgeF9lbmQgPSBjKGJib3gkeG1pbjpiYm94JHhtYXgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoYmJveCR4bWF4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChiYm94JHltaW46YmJveCR5bWF4KSkpLAogICAgICAgICAgICAgICAgICAgIHlfc3RhcnQgPSBjKHJlcChiYm94JHltaW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoYmJveCR4bWluOmJib3gkeG1heCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJib3gkeW1pbjpiYm94JHltYXgpLAogICAgICAgICAgICAgICAgICAgIHlfZW5kID0gYyhyZXAoYmJveCR5bWF4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKGJib3gkeG1pbjpiYm94JHhtYXgpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmJveCR5bWluOmJib3gkeW1heCkpCgojIFNoaWZ0IHRoZSBibGFkZSBhIHNtYWxsIGFtb3VudCB0byBhdm9pZCBwZXJmZWN0IG92ZXJsYXAgd2l0aCB1bmRlcmx5aW5nIGdyaWQKYmxhZGUgPC0gYmxhZGUgJT4lCiAgbXV0YXRlKGFjcm9zcyhldmVyeXRoaW5nKCksIAogICAgICAgICAgICAgICAgfiAueCArIDAuMjgpKQoKYmxhZGUgPC0gcG1hcChibGFkZSwgZnVuY3Rpb24oeF9zdGFydCwgeF9lbmQsIHlfc3RhcnQsIHlfZW5kKXsKICBzdF9saW5lc3RyaW5nKAogICAgbWF0cml4KAogICAgICBjKAogICAgICAgIHhfc3RhcnQsCiAgICAgICAgeV9zdGFydCwKICAgICAgICB4X2VuZCwKICAgICAgICB5X2VuZCksCiAgICAgIG5jb2wgPSAyLGJ5cm93ID0gVFJVRSkKICApCn0pICU+JQogIHN0X2FzX3NmYygpCmBgYAoKVXNlIHRoZSBibGFkZSBfYW5kXyB0aGUgcGF0aHMgdG8gc3BsaXQgdGhlIGxpbmVzOgpgYGB7cn0KIyBQYXJ0IDEKbW9zYWljX2xpbmVzXzEgPC0gbV8xX3VuaW9uICU+JQogIHN0X3NwbGl0KGJsYWRlKSAlPiUKICBzdF9jb2xsZWN0aW9uX2V4dHJhY3QodHlwZSA9ICJMSU5FU1RSSU5HIikgJT4lCiAgc3RfY2FzdCh0byA9ICJMSU5FU1RSSU5HIikgJT4lCiAgc3Rfc3BsaXQocGF0aHMpICU+JQogIHN0X2NvbGxlY3Rpb25fZXh0cmFjdCh0eXBlID0gIkxJTkVTVFJJTkciKSAlPiUKICBzdF9jYXN0KHRvID0gIkxJTkVTVFJJTkciKSAlPiUKICBtdXRhdGUoaWQgPSAiMSIpCgojIFBhcnQgMgptb3NhaWNfbGluZXNfMiA8LSBtXzJfdW5pb24gJT4lCiAgc3Rfc3BsaXQoYmxhZGUpICU+JQogIHN0X2NvbGxlY3Rpb25fZXh0cmFjdCh0eXBlID0gIkxJTkVTVFJJTkciKSAlPiUKICBzdF9jYXN0KHRvID0gIkxJTkVTVFJJTkciKSAlPiUKICBzdF9zcGxpdChwYXRocykgJT4lCiAgc3RfY29sbGVjdGlvbl9leHRyYWN0KHR5cGUgPSAiTElORVNUUklORyIpICU+JQogIHN0X2Nhc3QodG8gPSAiTElORVNUUklORyIpICU+JQogIG11dGF0ZShpZCA9ICIyIikKCiMgUGFydCAzCm1vc2FpY19saW5lc18zIDwtIG1fM191bmlvbiAlPiUKICBzdF9zcGxpdChibGFkZSkgJT4lCiAgc3RfY29sbGVjdGlvbl9leHRyYWN0KHR5cGUgPSAiTElORVNUUklORyIpICU+JQogIHN0X2Nhc3QodG8gPSAiTElORVNUUklORyIpICU+JQogIHN0X3NwbGl0KHBhdGhzKSAlPiUKICBzdF9jb2xsZWN0aW9uX2V4dHJhY3QodHlwZSA9ICJMSU5FU1RSSU5HIikgJT4lCiAgc3RfY2FzdCh0byA9ICJMSU5FU1RSSU5HIikgJT4lCiAgbXV0YXRlKGlkID0gIjMiKQoKIyBQYXJ0IDQKbW9zYWljX2xpbmVzXzQgPC0gbV80X3VuaW9uICU+JQogIHN0X3NwbGl0KGJsYWRlKSAlPiUKICBzdF9jb2xsZWN0aW9uX2V4dHJhY3QodHlwZSA9ICJMSU5FU1RSSU5HIikgJT4lCiAgc3RfY2FzdCh0byA9ICJMSU5FU1RSSU5HIikgJT4lCiAgc3Rfc3BsaXQocGF0aHMpICU+JQogIHN0X2NvbGxlY3Rpb25fZXh0cmFjdCh0eXBlID0gIkxJTkVTVFJJTkciKSAlPiUKICBzdF9jYXN0KHRvID0gIkxJTkVTVFJJTkciKSAlPiUKICBtdXRhdGUoaWQgPSAiNCIpCmBgYAoKRXh0cmFjdCB0aGUgZ2VvbWV0cmllcyBhbmQgc2VsZWN0IHRoZSBsaW5lIHNlZ21lbnRzIHRoYXQgYXJlIHdpdGhpbiBlYWNoIG9mIHRoZSBjb250YWluZXIgcG9seWdvbnM6CmBgYHtyfQojIG1vc2FpY19saW5lc18xIDwtIG1vc2FpY19saW5lc18xW2NvbnRhaW5lciAlPiUgZmlsdGVyKGlkID09ICIxIiksXQojIAojbW9zYWljX2xpbmVzXzIgPC0gbW9zYWljX2xpbmVzXzJbY29udGFpbmVyICU+JSBmaWx0ZXIoaWQgPT0gIjIiKSxdCgojbW9zYWljX2xpbmVzXzMgPC0gbW9zYWljX2xpbmVzXzNbY29udGFpbmVyICU+JSBmaWx0ZXIoaWQgPT0gIjMiKSxdCiMgCiMgbW9zYWljX2xpbmVzXzQgPC0gbW9zYWljX2xpbmVzXzRbY29udGFpbmVyICU+JSBmaWx0ZXIoaWQgPT0gIjQiKSxdCgpgYGAKClB1dCB0b2dldGhlcjoKYGBge3J9CiMgbW9zYWljX2xpbmVzIDwtIHJiaW5kKG1vc2FpY19saW5lc18xW2NvbnRhaW5lciAlPiUgZmlsdGVyKGlkID09ICIxIiksXSwKIyAgICAgICAgICAgICAgICAgICAgICAgbW9zYWljX2xpbmVzXzJbY29udGFpbmVyICU+JSBmaWx0ZXIoaWQgPT0gIjIiKSxdLAojICAgICAgICAgICAgICAgICAgICAgICBtb3NhaWNfbGluZXNfM1tjb250YWluZXIgJT4lIGZpbHRlcihpZCA9PSAiMyIpLF0sCiMgICAgICAgICAgICAgICAgICAgICAgIG1vc2FpY19saW5lc180W2NvbnRhaW5lciAlPiUgZmlsdGVyKGlkID09ICI0IiksXSkKbW9zYWljX2xpbmVzIDwtIHJiaW5kKG1vc2FpY19saW5lc18xLAogICAgICAgICAgICAgICAgICAgICAgbW9zYWljX2xpbmVzXzJbY29udGFpbmVyICU+JSBmaWx0ZXIoaWQgPT0gIjIiKSxdLAogICAgICAgICAgICAgICAgICAgICAgbW9zYWljX2xpbmVzXzNbY29udGFpbmVyICU+JSBmaWx0ZXIoaWQgPT0gIjMiKSxdLAogICAgICAgICAgICAgICAgICAgICAgbW9zYWljX2xpbmVzXzQpCmBgYAoKRmluZCB0aGUgbmVhcmVzdCBmZWF0dXJlIGFuZCBib3Jyb3cgdG9uZXMgb2YgZ3JheSBhbmQgaGV4YWRlY2ltYWwgY29sb3JzOgpgYGB7cn0KY29sb3JzX2RmIDwtIGRmX3NmW21vc2FpY19saW5lcyAlPiUgCiAgICAgICAgICAgICAgICAgICAgIHN0X25lYXJlc3RfZmVhdHVyZShkZl9zZiksXQpgYGAKCldlIGNhbiBub3cgYWRkIHRoZSBncmV5c2NhbGUgdmFsdWVzIGFuZCBoZXhhZGVjaW1hbCBjb2xvcnMgdG8gdGhlIGRhdGEgZnJhbWUgd2l0aCB0aGUgbW9zYWljOgpgYGB7cn0KbW9zYWljX2xpbmVzJHZhbHVlIDwtIGNvbG9yc19kZiR2YWx1ZQptb3NhaWNfbGluZXMkaGV4X2NvbG9yIDwtIGNvbG9yc19kZiRoZXhfY29sb3IKYGBgCgpDcmVhdGUgcGFydHMgb2YgbW9zYWljOgpgYGB7cn0Kc2t5XzEgPC0gY29udGFpbmVyICU+JSAKICBmaWx0ZXIoaWQgPT0gNCkgJT4lIAogIHN0X2J1ZmZlcihkaXN0ID0gMykgJT4lIAogIHN0X2Nyb3AoY29udGFpbmVyKQoKc2t5XzIgPC0gY29udGFpbmVyICU+JSAKICBmaWx0ZXIoaWQgPT0gMSkgJT4lIAogIHN0X2J1ZmZlcihkaXN0ID0gOSkgJT4lIAogIHN0X2Nyb3AoY29udGFpbmVyKQoKY2xvdWQgPC0gY29udGFpbmVyICU+JSAKICBmaWx0ZXIoaWQgPT0gMikgJT4lIAogIHN0X2J1ZmZlcihkaXN0ID0gOSkgJT4lIAogIHN0X2Nyb3AoY29udGFpbmVyKQpgYGAKClBsb3QgbW9zYWljIChtb25vdG9uZSk6CmBgYHtyIGV2YWw9RkFMU0V9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBjbG91ZCwKICAgICAgICAgIGNvbG9yID0gTkEsCiAgICAgICAgICBmaWxsID0gIndoaXRlIikgKwogIGdlb21fc2YoZGF0YSA9IG1vc2FpY19saW5lcyAlPiUKICAgICAgICAgICAgZmlsdGVyKGlkID09IDIgfCBpZCA9PSAzKSAlPiUKICAgICAgICAgICAgc3RfY3JvcChkZl9zZiksCiAgICAgICAgICBhZXMoc2l6ZSA9IGV4cCgtNSAqIHZhbHVlKSwKICAgICAgICAgICAgICBjb2xvciA9IHZhbHVlKSkgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDAuMDEsIDEuMykpICsgCiAgZ2duZXdzY2FsZTo6bmV3X3NjYWxlKCJzaXplIikgKwogIGdlb21fc2YoZGF0YSA9IHNreV8yLAogICAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgZmlsbCA9ICJkZWVwc2t5Ymx1ZTQiKSArCiAgZ2VvbV9zZihkYXRhID0gbW9zYWljX2xpbmVzW3NreV8yLF0gJT4lCiAgICAgICAgICAgIGZpbHRlcihpZCA9PSAiMSIpICU+JQogICAgICAgICAgICBzdF9jcm9wKGRmX3NmKSwKICAgICAgICAgIGFlcyhzaXplID0gZXhwKDEgKiB2YWx1ZSkpLAogICAgICAgICAgY29sb3IgPSAid2hpdGUiKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMC4wMSwgMC4zKSkgKwogIGdlb21fc2YoZGF0YSA9IHNreV8xLCAKICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwKICAgICAgICAgIGZpbGwgPSAiZ29sZGVucm9kMiIpICsKICBnZW9tX3NmKGRhdGEgPSBtb3NhaWNfbGluZXNbc2t5XzEsXSAlPiUKICAgICAgICAgICAgZmlsdGVyKGlkID09ICI0IikgJT4lCiAgICAgICAgICAgIHN0X2Nyb3AoZGZfc2YpLAogICAgICAgICAgYWVzKHNpemUgPSBleHAoMSAqIHZhbHVlKSksCiAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIpICsKICBjb29yZF9zZihleHBhbmQgPSBGQUxTRSkgKyAKICB0aGVtZV92b2lkKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMC4xLCAwLjEsIDAuMSwgMC4xLCAiaW4iKSkKCmdnc2F2ZSgidHJ1Y2hldC1jbG91ZHMtbW9ub3RvbmUucG5nIiwKICAgICAgIGhlaWdodCA9IDcsCiAgICAgICB3aWR0aCA9IDcsCiAgICAgICB1bml0cyA9ICJpbiIpCmBgYAo=